From d585b0e042fab275306e06f53978e77d2ed758ee Mon Sep 17 00:00:00 2001 From: marco Date: Mon, 20 Jan 2020 18:31:32 +0100 Subject: [PATCH] KoD 0.7.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A grande richiesta, è ora possibile riprodurre in automatico l'episodio successivo di una serie in libreria - aggiunta la possibilità di nascondere la lista dei server, quando si usa l'autoplay - aggiunto canale pufimovies.com - fix vari --- README.md | 1 + addon.xml | 9 +- channels.json | 16 +- channels/altadefinizione01.json | 4 +- channels/altadefinizione01.py | 2 +- channels/altadefinizione01_link.py | 2 +- channels/altadefinizioneclick.json | 4 +- channels/altadefinizioneclick.py | 5 +- channels/animeforce.py | 143 +++++--- channels/animeleggendari.py | 10 +- channels/animesaturn.json | 76 +--- channels/animesaturn.py | 41 ++- channels/animesubita.py | 52 ++- channels/animetubeita.py | 4 +- channels/animeunity.json | 11 + channels/animeunity.py | 123 +++++++ channels/animeworld.py | 18 +- channels/casacinema.py | 229 +++++------- channels/cb01anime.py | 12 +- channels/cineblog01.py | 2 +- channels/cinemalibero.py | 6 +- channels/cinetecadibologna.py | 2 +- channels/dreamsub.json | 1 - channels/dreamsub.py | 327 +++++------------- channels/fastsubita.py | 6 +- channels/filmpertutti.py | 32 +- channels/ilgeniodellostreaming.py | 6 +- channels/italiaserie.json | 4 +- channels/piratestreaming.py | 2 +- channels/pufimovies.json | 11 + channels/pufimovies.py | 111 ++++++ channels/seriehd.py | 6 +- channels/serietvsubita.py | 14 +- channels/serietvu.py | 12 +- channels/streamtime.py | 6 +- channels/tantifilm.py | 8 +- channels/toonitalia.py | 2 +- channels/vedohd.json | 31 +- channels/vvvvid.py | 6 +- channelselector.py | 4 +- core/scraper.py | 2 +- core/support.py | 118 +++++-- core/tmdb.py | 51 +-- lib/cloudscraper/__init__.py | 70 +++- lib/unshortenit.py | 2 +- platformcode/launcher.py | 119 ++++--- platformcode/platformtools.py | 16 +- platformcode/subtitletools.py | 2 +- platformcode/updater.py | 8 +- resources/language/English/strings.po | 34 +- resources/language/Italian/strings.po | 36 +- resources/settings.xml | 82 +---- resources/skins/Default/720p/NextDialog.xml | 88 +++++ .../skins/Default/720p/NextDialogCompact.xml | 89 +++++ .../media/NextDialog/background-diffuse.png | Bin 0 -> 1627 bytes .../Default/media/NextDialog/background.png | Bin 0 -> 2088 bytes .../Default/media/NextDialog/close-fo.png | Bin 0 -> 11255 bytes .../Default/media/NextDialog/close-nf.png | Bin 0 -> 11226 bytes .../Default/media/NextDialog/play-fo.png | Bin 0 -> 1823 bytes .../Default/media/NextDialog/play-nf.png | Bin 0 -> 1814 bytes .../Default/media/Shortcut - Copia/close.png | Bin 0 -> 874 bytes .../dialog-bg-solid-white.png | Bin 0 -> 3774 bytes .../Shortcut - Copia/dialog-bg-solid.png | Bin 0 -> 1891 bytes .../Default/media/Shortcut - Copia/logo.png | Bin 0 -> 838 bytes .../Default/media/Shortcut - Copia/white.png | Bin 0 -> 167 bytes .../media/Shortcut - Copia/white70.png | Bin 0 -> 177 bytes servers/akstream.py | 2 +- servers/cloudvideo.py | 9 +- servers/decrypters/zcrypt.py | 8 +- servers/mixdrop.json | 2 +- servers/supervideo.py | 28 +- servers/vidoza.py | 7 +- servers/vup.json | 42 --- servers/vup.py | 28 -- servers/vupplayer.py | 5 +- servers/wstream.json | 2 +- servers/wstream.py | 28 +- specials/community.py | 8 +- specials/nextep.py | 180 ++++++++++ specials/resolverdns.py | 4 +- specials/videolibrary.py | 6 +- 81 files changed, 1447 insertions(+), 990 deletions(-) create mode 100644 channels/animeunity.json create mode 100644 channels/animeunity.py create mode 100644 channels/pufimovies.json create mode 100644 channels/pufimovies.py create mode 100644 resources/skins/Default/720p/NextDialog.xml create mode 100644 resources/skins/Default/720p/NextDialogCompact.xml create mode 100644 resources/skins/Default/media/NextDialog/background-diffuse.png create mode 100644 resources/skins/Default/media/NextDialog/background.png create mode 100644 resources/skins/Default/media/NextDialog/close-fo.png create mode 100644 resources/skins/Default/media/NextDialog/close-nf.png create mode 100644 resources/skins/Default/media/NextDialog/play-fo.png create mode 100644 resources/skins/Default/media/NextDialog/play-nf.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/close.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid-white.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/logo.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/white.png create mode 100644 resources/skins/Default/media/Shortcut - Copia/white70.png delete mode 100644 servers/vup.json delete mode 100644 servers/vup.py create mode 100644 specials/nextep.py diff --git a/README.md b/README.md index 9b2d921b..16e630af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Kodi On Demand ### Un fork italiano di [Alfa](https://github.com/alfa-addon) Ognuno è libero (anzi, invitato!) a collaborare, per farlo è possibile utilizzare i pull request. +KOD funziona con Kodi fino alla versione 18 (Python 2). KOD, come Alfa, è sotto licenza GPL v3, pertanto siete liberi di utilizzare parte del codice, a patto di rispettare i termini di suddetta licenza, che si possono riassumere in: diff --git a/addon.xml b/addon.xml index 08ef47c0..4550f759 100644 --- a/addon.xml +++ b/addon.xml @@ -1,4 +1,4 @@ - + @@ -19,9 +19,10 @@ resources/media/themes/ss/2.png resources/media/themes/ss/3.png - - nuovo metodo di override DNS -- aggiunta opzione nascondi server, se usi l'autoplay -- migliorie al codice e fix vari + - A grande richiesta, è ora possibile riprodurre in automatico l'episodio successivo di una serie in libreria +- aggiunta la possibilità di nascondere la lista dei server, quando si usa l'autoplay +- aggiunto canale pufimovies.com +- fix vari Naviga velocemente sul web e guarda i contenuti presenti [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] [COLOR yellow]Kodi © is a registered trademark of the XBMC Foundation. We are not connected to or in any other way affiliated with Kodi, Team Kodi, or the XBMC Foundation. Furthermore, any software, addons, or products offered by us will receive no support in official Kodi channels, including the Kodi forums and various social networks.[/COLOR] diff --git a/channels.json b/channels.json index 6e027a09..3f9214aa 100644 --- a/channels.json +++ b/channels.json @@ -2,18 +2,19 @@ "altadefinizione01": "https://www.altadefinizione01.tel", "altadefinizione01_link": "https://altadefinizione01.cam", "animeforce": "https://ww1.animeforce.org", - "animeleggendari": "https://animepertutti.com", + "animeleggendari": "https://animepertutti.com", + "animesaturn": "https://animesaturn.com", "animestream": "https://www.animeworld.it", "animesubita": "http://www.animesubita.org", "animetubeita": "http://www.animetubeita.com", - "animeworld": "https://www1.animeworld.tv", - "casacinema": "https://www.casacinema.cloud", + "animeworld": "https://www.animeworld.cc", + "animeunity": "https://www.animeunity.it", + "casacinema": "https://www.casacinema.biz", "casacinemaInfo": "https://casacinema.kim", - "cb01anime": "https://www.cineblog01.ink", - "cinemalibero": "https://www.cinemalibero.live", + "cb01anime": "https://www.cineblog01.ink", "cinetecadibologna": "http://cinestore.cinetecadibologna.it", "documentaristreamingda": "https://documentari-streaming-da.com", - "dreamsub": "https://www.dreamsub.stream", + "dreamsub": "https://dreamsub.stream", "fastsubita": "https://fastsubita.com", "filmgratis": "https://www.filmaltadefinizione.org", "filmigratis": "https://filmigratis.org", @@ -26,9 +27,10 @@ "ilgeniodellostreaming": "https://igds.one", "italiaserie": "https://italiaserie.org", "mondoserietv": "https://mondoserietv.com", - "netfreex": "https://www.netfreex.online", + "netfreex": "https://www.netfreex.icu", "piratestreaming": "https://www.piratestreaming.gratis", "polpotv": "https://polpo.tv", + "pufimovies": "https://pufimovies.com", "seriehd": "https://www.seriehd.watch", "serietvonline": "https://serietvonline.monster", "serietvsubita": "http://serietvsubita.xyz", diff --git a/channels/altadefinizione01.json b/channels/altadefinizione01.json index 8f0d698a..f7589d50 100644 --- a/channels/altadefinizione01.json +++ b/channels/altadefinizione01.json @@ -4,8 +4,8 @@ "language": ["ita", "sub-ita"], "active": true, "adult": false, - "thumbnail": "https://raw.githubusercontent.com/Zanzibar82/images/master/posters/altadefinizione01.png", - "banner": "https://raw.githubusercontent.com/Zanzibar82/images/master/posters/altadefinizione01.png", + "thumbnail": "altadefinizione01.png", + "banner": "altadefinizione01.png", "categories": ["movie", "vos"], "settings": [] } diff --git a/channels/altadefinizione01.py b/channels/altadefinizione01.py index afc6e518..ab2f9e1f 100644 --- a/channels/altadefinizione01.py +++ b/channels/altadefinizione01.py @@ -28,7 +28,7 @@ def findhost(): host = config.get_channel_url(findhost) headers = [['Referer', host]] -list_servers = ['verystream','openload','rapidvideo','streamango'] +list_servers = ['mixdrop','vidoza','cloudvideo','vup','supervideo','gounlimited'] list_quality = ['default'] @support.menu diff --git a/channels/altadefinizione01_link.py b/channels/altadefinizione01_link.py index c0c451a8..a73bde31 100644 --- a/channels/altadefinizione01_link.py +++ b/channels/altadefinizione01_link.py @@ -26,7 +26,7 @@ __channel__ = "altadefinizione01_link" host = config.get_channel_url() headers = [['Referer', host]] -list_servers = ['supervideo', 'streamcherry','rapidvideo', 'streamango', 'openload'] +list_servers = ['mixdrop', 'vup', 'supervideo'] list_quality = ['default'] # =========== home menu =================== diff --git a/channels/altadefinizioneclick.json b/channels/altadefinizioneclick.json index d5190e8c..8ca67198 100644 --- a/channels/altadefinizioneclick.json +++ b/channels/altadefinizioneclick.json @@ -4,8 +4,8 @@ "active": true, "adult": false, "language": ["ita","sub-ita"], - "thumbnail": "https:\/\/raw.githubusercontent.com\/Zanzibar82\/images\/master\/posters\/altadefinizioneclick.png", - "bannermenu": "https:\/\/raw.githubusercontent.com\/Zanzibar82\/images\/master\/posters\/altadefinizioneciclk.png", + "thumbnail": "altadefinizioneclick.png", + "bannermenu": "altadefinizioneciclk.png", "categories": ["movie","vos"], "settings": [] } diff --git a/channels/altadefinizioneclick.py b/channels/altadefinizioneclick.py index fd7a2da1..48631174 100644 --- a/channels/altadefinizioneclick.py +++ b/channels/altadefinizioneclick.py @@ -28,9 +28,8 @@ def findhost(): host = config.get_channel_url(findhost) headers = [['Referer', host]] -list_servers = ['verystream', 'rapidvideo', 'openload', 'streamango', 'vidoza', - 'vidcloud', 'thevideo', 'okru', 'hdload', 'youtube'] -list_quality = ['1080p', '720', '360'] +list_servers = ['mixdrop', 'vidcloud', 'vidoza', 'supervideo', 'hdload', 'mystream'] +list_quality = ['1080p', '720p', '360p'] @support.menu def mainlist(item): diff --git a/channels/animeforce.py b/channels/animeforce.py index cf2dccfc..565e395c 100644 --- a/channels/animeforce.py +++ b/channels/animeforce.py @@ -3,14 +3,11 @@ # Canale per AnimeForce # ------------------------------------------------------------ -from servers.decrypters import adfly from core import support host = support.config.get_channel_url() -IDIOMAS = {'Italiano': 'IT'} -list_language = IDIOMAS.values() -list_servers = ['directo', 'openload', 'vvvvid'] +list_servers = ['directo', 'vvvvid'] list_quality = ['default'] @@ -45,47 +42,40 @@ def newest(categoria): return itemlist -@support.scrape def search(item, texto): - # debug = True - search = texto + support.log(texto) + item.args = 'noorder' + item.url = host + '/?s=' + texto + '&cat=6010' item.contentType = 'tvshow' - patron = r'\s*]+>(?P[^<]+)<' - action = 'episodios' - return locals() + try: + return peliculas(item) + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.logger.error("%s" % line) + return [] @support.scrape def peliculas(item): anime = True action = 'episodios' - - if item.args == 'newest': - patron = r'<a href="(?P<url>[^"]+)">\s*<img src="(?P<thumb>[^"]+)" alt="(?P<title>.*?)(?: Sub| sub| SUB|")' - action = 'findvideos' - - elif item.args == 'last': - patron = r'<a href="(?P<url>[^"]+)">\s*<img src="(?P<thumb>[^"]+)" alt="(?P<title>.*?)(?: Sub| sub| SUB|")' - + if not item.args: + pagination = '' + patron = r'<a\s*href="(?P<url>[^"]+)"\s*title="(?P<title>[^"]+)">' elif item.args == 'corso': pagination = '' patron = r'<strong><a href="(?P<url>[^"]+)">(?P<title>.*?) [Ss][Uu][Bb]' else: - pagination = '' - patron = r'<a href="(?P<url>[^"]+)">\s*<strong[^>]+>(?P<title>[^<]+)<' + patron = r'<a href="(?P<url>[^"]+)"[^>]+>\s*<img src="(?P<thumb>[^"]+)" alt="(?P<title>.*?)(?: Sub| sub| SUB|")' + + if item.args == 'newest': item.action = 'findvideos' def itemHook(item): if 'sub-ita' in item.url: if item.args != 'newest': item.title = item.title + support.typo('Sub-ITA','_ [] color kod') item.contentLanguage = 'Sub-ITA' - if item.args == 'newest': - url = support.match(item, '<a href="([^"]+)" title="[^"]+" target="[^"]+" class="btn', headers=headers)[0] - item.url = url[0] if url else '' - delete = support.scrapertools.find_single_match(item.fulltitle, r'( Episodi.*)') - episode = support.scrapertools.find_single_match(item.title, r'Episodi(?:o)? (?:\d+÷)?(\d+)') - item.title = support.typo(episode + ' - ','bold') + item.title.replace(delete,'') - item.fulltitle = item.show = item.title.replace(delete,'') - item.episode = episode return item return locals() @@ -94,9 +84,15 @@ def peliculas(item): @support.scrape def episodios(item): anime = True - patron = r'<td style[^>]+>\s*.*?(?:<span[^>]+)?<strong>(?P<title>[^<]+)<\/strong>.*?<td style[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>' + data = support.match(item, headers=headers).data + if '<h6>Streaming</h6>' in data: + patron = r'<td style[^>]+>\s*.*?(?:<span[^>]+)?<strong>(?P<title>[^<]+)<\/strong>.*?<td style[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>' + else: + patron = r'<a\s*href="(?P<url>[^"]+)"\s*title="(?P<title>[^"]+)"\s*class="btn btn-dark mb-1">' def itemHook(item): - item.url = item.url.replace(host, '') + support.log(item) + if item.url.startswith('//'): item.url= 'https:' + item.url + elif item.url.startswith('/'): item.url= 'https:/' + item.url return item action = 'findvideos' return locals() @@ -104,38 +100,81 @@ def episodios(item): def findvideos(item): support.log(item) - + # try: + # from urlparse import urljoin + # except: + # from urllib.parse import urljoin + # support.dbg() itemlist = [] - - if item.episode: - from lib import unshortenit - url, c = unshortenit.unshorten(item.url) - url = support.match(item, r'<a href="([^"]+)"[^>]*>', patronBlock=r'Episodio %s(.*?)</tr>' % item.episode ,url=url)[0] - item.url = url[0] if url else '' - if 'vvvvid' in item.url: - item.action = 'play' - itemlist.append(item) + import requests + from lib import vvvvid_decoder + + if support.match(item.url, string=True, patron=r'(\d+/\d+)').match: + item.action = 'play' + itemlist.append(item) + else: + # VVVVID vars + vvvvid_host = 'https://www.vvvvid.it/vvvvid/ondemand/' + vvvvid_headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0'} - if 'http' not in item.url: - if '//' in item.url[:2]: - item.url = 'http:' + item.url - elif host not in item.url: - item.url = host + item.url + # VVVVID session + current_session = requests.Session() + login_page = 'https://www.vvvvid.it/user/login' + conn_id = current_session.get(login_page, headers=vvvvid_headers).json()['data']['conn_id'] + payload = {'conn_id': conn_id} + + + # collect parameters + show_id = support.match(item.url, string=True, patron=r'(\d+)').match + ep_number = support.match(item.title, patron=r'(\d+)').match + json_file = current_session.get(vvvvid_host + show_id + '/seasons/', headers=vvvvid_headers, params=payload).json() + season_id = str(json_file['data'][0]['season_id']) + json_file = current_session.get(vvvvid_host + show_id + '/season/' + season_id +'/', headers=vvvvid_headers, params=payload).json() + + # select the correct episode + for episode in json_file['data']: + support.log('Number',int(episode['number']),int(ep_number)) + if int(episode['number']) == int(ep_number): + url = vvvvid_decoder.dec_ei(episode['embed_info'] or episode['embed_info']) + if 'youtube' in url: item.url = url + item.url = url.replace('manifest.f4m','master.m3u8').replace('http://','https://').replace('/z/','/i/') + if 'https' not in item.url: + url = support.match(item, url='https://or01.top-ix.org/videomg/_definst_/mp4:' + item.url + '/playlist.m3u')[1] + url = url.split()[-1] + itemlist.append( + support.Item(action= 'play', + url= 'https://or01.top-ix.org/videomg/_definst_/mp4:' + item.url + '/' + url, + server= 'directo')) + + elif 'adf.ly' in item.url: + from servers.decrypters import adfly + url = adfly.get_long_url(item.url) - if 'adf.ly' in item.url: - item.url = adfly.get_long_url(item.url) elif 'bit.ly' in item.url: - item.url = support.httptools.downloadpage(item.url, only_headers=True, follow_redirects=False).headers.get("location") + url = support.httptools.downloadpage(item.url, only_headers=True, follow_redirects=False).headers.get("location") + + else: + url = host + for u in item.url.split('/'): + # support.log(i) + if u and 'animeforce' not in u and 'http' not in u: + url += '/' + u + + if 'php?' in url: + url = support.httptools.downloadpage(url, only_headers=True, follow_redirects=False).headers.get("location") + url = support.match(url, patron=r'class="button"><a href=(?:")?([^" ]+)', headers=headers).match + else: + url = support.match(url, patron=[r'<source src=(?:")?([^" ]+)',r'name="_wp_http_referer" value="([^"]+)"']).match + if url.startswith('//'): url = 'https:' + url + elif url.startswith('/'): url = 'https:/' + url - matches = support.match(item, r'button"><a href="([^"]+)"')[0] - for video in matches: itemlist.append( support.Item(channel=item.channel, action="play", - title='diretto', - url=video, + title='Diretto', + url=url, server='directo')) return support.server(item, itemlist=itemlist) diff --git a/channels/animeleggendari.py b/channels/animeleggendari.py index f3fd8b00..a26e8314 100644 --- a/channels/animeleggendari.py +++ b/channels/animeleggendari.py @@ -74,10 +74,10 @@ def peliculas(item): @support.scrape def episodios(item): - data = support.match(item, headers=headers)[1] - if not any(x in data for x in ['Lista Episodi', 'Movie Parte']): + data = support.match(item, headers=headers, patronBlock=r'entry-content clearfix">(.*?)class="mh-widget mh-posts-2 widget_text').block + if not 'pagination clearfix' in data: support.log('NOT IN DATA') - patron = r'(?:iframe src|str)="(?P<url>[^"]+)"' + patron = r'<iframe.*?src="(?P<url>[^"]+)"' title = item.title def fullItemlistHook(itemlist): url = '' @@ -107,7 +107,7 @@ def episodios(item): return locals() def check(item): - data = support.match(item, headers=headers)[1] + data = support.match(item, headers=headers).data if 'Lista Episodi' not in data: item.data = data return findvideos(item) @@ -120,7 +120,7 @@ def findvideos(item): if item.data: data = item.data else: - matches = support.match(item, '(?:str="([^"]+)"|iframe src="([^"]+)")')[0] + matches = support.match(item, patron=r'<iframe.*?src="(?P<url>[^"]+)"').matches data = '' if matches: for match in matches: diff --git a/channels/animesaturn.json b/channels/animesaturn.json index c2e68a8a..e409d3c4 100644 --- a/channels/animesaturn.json +++ b/channels/animesaturn.json @@ -7,79 +7,5 @@ "thumbnail": "animesaturn.png", "banner": "animesaturn.png", "categories": ["anime"], - "settings": [ - { - "id": "modo_grafico", - "type": "bool", - "label": "Cerca informazioni extra", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "channel_host", - "type": "text", - "label": "Host del canale", - "default": "https://www.animesaturn.com", - "enabled": true, - "visible": true - }, - { - "id": "include_in_global_search", - "type": "bool", - "label": "Includi ricerca globale", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "include_in_newest_anime", - "type": "bool", - "label": "Includi in Novità - Anime", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "include_in_newest_italiano", - "type": "bool", - "label": "Includi in Novità - Italiano", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "checklinks", - "type": "bool", - "label": "Verifica se i link esistono", - "default": false, - "enabled": true, - "visible": true - }, - { - "id": "checklinks_number", - "type": "list", - "label": "Numero de link da verificare", - "default": 1, - "enabled": true, - "visible": "eq(-1,true)", - "lvalues": [ "1", "3", "5", "10" ] - }, - { - "id": "autorenumber", - "type": "bool", - "label": "@70712", - "default": false, - "enabled": true, - "visible": true - }, - { - "id": "autorenumber_mode", - "type": "bool", - "label": "@70688", - "default": false, - "enabled": true, - "visible": "eq(-1,true)" - } - ] + "settings": [] } diff --git a/channels/animesaturn.py b/channels/animesaturn.py index fd51ec44..cf528b3d 100644 --- a/channels/animesaturn.py +++ b/channels/animesaturn.py @@ -6,13 +6,13 @@ from core import support -__channel__ = "animesaturn" -host = support.config.get_setting("channel_host", __channel__) +# __channel__ = "animesaturn" +# host = support.config.get_setting("channel_host", __channel__) +host = support.config.get_channel_url() headers={'X-Requested-With': 'XMLHttpRequest'} -IDIOMAS = {'Italiano': 'ITA'} -list_language = IDIOMAS.values() -list_servers = ['openload', 'fembed', 'animeworld'] + +list_servers = ['directo', 'fembed', 'animeworld'] list_quality = ['default', '480p', '720p', '1080p'] @support.menu @@ -33,6 +33,9 @@ def search(item, texto): anime = True patron = r'href="(?P<url>[^"]+)"[^>]+>[^>]+>(?P<title>[^<|(]+)(?:(?P<lang>\(([^\)]+)\)))?<|\)' action = 'check' + def itemHook(item): + item.url = item.url.replace('www.','') + return item return locals() @@ -58,6 +61,9 @@ def newest(categoria): def menu(item): patronMenu = r'u>(?P<title>[^<]+)<u>(?P<url>.*?)</div> </div>' action = 'peliculas' + def itemHook(item): + item.url = item.url.replace('www.','') + return item return locals() @@ -67,7 +73,7 @@ def peliculas(item): deflang= 'Sub-ITA' if item.args == 'updated': post = "page=" + str(item.page if item.page else 1) if item.page > 1 else None - page, data = support.match(item, r'data-page="(\d+)" title="Next">', post=post, headers=headers) + page= support.match(item, patron=r'data-page="(\d+)" title="Next">', post=post, headers=headers).match patron = r'<img alt="[^"]+" src="(?P<thumb>[^"]+)" [^>]+></div></a>\s*<a href="(?P<url>[^"]+)"><div class="testo">(?P<title>[^\(<]+)(?:(?P<lang>\(([^\)]+)\)))?</div></a>\s*<a href="[^"]+"><div class="testo2">[^\d]+(?P<episode>\d+)</div></a>' if page: nextpage = page item.contentType='episode' @@ -78,17 +84,20 @@ def peliculas(item): action = 'check' else: pagination = '' - if item.args == 'incorso': patron = r'"slider_title" href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)"[^>]+>(?P<title>[^\(<]+)(?:\((?P<year>\d+)\))?</a>' + if item.args == 'incorso': patron = r'"slider_title"\s*href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)"[^>]+>(?P<title>[^\(<]+)(?:\((?P<year>\d+)\))?</a>' else: patron = r'href="(?P<url>[^"]+)"[^>]+>[^>]+>(?P<title>.+?)(?:\((?P<lang>ITA)\))?(?:(?P<year>\((\d+)\)))?</span>' action = 'check' + def itemHook(item): + item.url = item.url.replace('www.','') + return item return locals() def check(item): - movie, data = support.match(item, r'Episodi:</b> (\d*) Movie') - anime_id = support.match(data, r'anime_id=(\d+)')[0][0] + movie = support.match(item, patron=r'Episodi:</b> (\d*) Movie') + anime_id = support.match(movie.data, patron=r'anime_id=(\d+)').match item.url = host + "/loading_anime?anime_id=" + anime_id - if movie: + if movie.match: item.contentType = 'movie' episodes = episodios(item) if len(episodes) > 0: item.url = episodes[0].url @@ -102,16 +111,20 @@ def check(item): def episodios(item): if item.contentType != 'movie': anime = True patron = r'<strong" style="[^"]+">(?P<title>[^<]+)</b></strong></td>\s*<td style="[^"]+"><a href="(?P<url>[^"]+)"' + def itemHook(item): + item.url = item.url.replace('www.','') + return item return locals() def findvideos(item): support.log() itemlist = [] - urls = support.match(item, r'<a href="([^"]+)"><div class="downloadestreaming">', headers=headers)[0] + # support.dbg() + urls = support.match(item, patron=r'<a href="([^"]+)"><div class="downloadestreaming">', headers=headers, debug=False).matches if urls: - links = support.match(item, r'(?:<source type="[^"]+"\s*src=|file:\s*)"([^"]+)"', url=urls[0], headers=headers)[0] - for link in links: + links = support.match(urls[0].replace('www.',''), patron=r'(?:<source type="[^"]+"\s*src=|file:\s*)"([^"]+)"', headers=headers, debug=False) + for link in links.matches: itemlist.append( support.Item(channel=item.channel, action="play", @@ -123,7 +136,7 @@ def findvideos(item): show=item.show, contentType=item.contentType, folder=False)) - return support.server(item, itemlist=itemlist) + return support.server(item, links, itemlist=itemlist) diff --git a/channels/animesubita.py b/channels/animesubita.py index 488f06d8..0df2dc84 100644 --- a/channels/animesubita.py +++ b/channels/animesubita.py @@ -34,7 +34,7 @@ def newest(categoria): if itemlist[-1].action == "ultimiep": itemlist.pop() - # Continua l'esecuzione in caso di errore + # Continua l'esecuzione in caso di errore except: import sys for line in sys.exc_info(): @@ -50,7 +50,7 @@ def search(item, texto): item.args = 'alt' try: return peliculas(item) - # Continua la ricerca in caso di errore + # Continua la ricerca in caso di errore except: import sys for line in sys.exc_info(): @@ -67,10 +67,9 @@ def genres(item): @support.scrape -def peliculas(item): +def peliculas(item): anime = True if item.args == 'updated': - #patron = r'<div class="post-thumbnail">\s*<a href="(?P<url>[^"]+)" title="(?P<title>.*?)\s*(?P<episode>Episodio \d+)[^"]+"[^>]*>\s*<img[^src]+src="(?P<thumb>[^"]+)"' patron = r'<div class="post-thumbnail">\s*<a href="(?P<url>[^"]+)" title="(?P<title>.*?)\s*Episodio (?P<episode>\d+) (?P<lang>[a-zA-Z-\s]+)[^"]*"> <img[^src]+src="(?P<thumb>[^"]+)"' patronNext = r'<link rel="next" href="([^"]+)"\s*/>' action = 'findvideos' @@ -98,32 +97,27 @@ def findvideos(item): itemlist = [] if item.args == 'updated': - ep = support.match(item.fulltitle,r'(Episodio\s*\d+)')[0][0] - item.url = support.re.sub(r'episodio-\d+-|oav-\d+-', '',item.url) + ep = support.match(item.fulltitle, patron=r'(\d+)').match + item.url = support.re.sub(r'episodio-\d+-|oav-\d+-'+ep, '',item.url) if 'streaming' not in item.url: item.url = item.url.replace('sub-ita','sub-ita-streaming') - item.url = support.match(item, r'<a href="([^"]+)"[^>]+>', ep + '(.*?)</tr>', )[0][0] + item.url = support.match(item, patron= ep + r'[^>]+>[^>]+>[^>]+><a href="([^"]+)"').match - urls = support.match(item.url, r'(episodio\d*.php.*)')[0] - for url in urls: - url = host + '/' + url - headers['Referer'] = url - data = support.match(item, headers=headers, url=url)[1] - cookies = "" - matches = support.re.compile('(.%s.*?)\n' % host.replace("http://", "").replace("www.", ""), support.re.DOTALL).findall(support.config.get_cookie_data()) - for cookie in matches: - cookies += cookie.split('\t')[5] + "=" + cookie.split('\t')[6] + ";" + # post + url = host + '/' + support.match(item.url, patron=r'(episodio\d*.php.*?)"').match.replace('%3F','?').replace('%3D','=') + headers['Referer'] = url + cookies = "" + matches = support.re.compile('(.%s.*?)\n' % host.replace("http://", "").replace("www.", ""), support.re.DOTALL).findall(support.config.get_cookie_data()) + for cookie in matches: + cookies += cookie.split('\t')[5] + "=" + cookie.split('\t')[6] + ";" + headers['Cookie'] = cookies[:-1] - headers['Cookie'] = cookies[:-1] - - url = support.match(data, r'<source src="([^"]+)"[^>]+>')[0][0] + '|' + support.urllib.urlencode(headers) - itemlist.append( - support.Item(channel=item.channel, - action="play", - title='diretto', - quality='', - url=url, - server='directo', - fulltitle=item.fulltitle, - show=item.show)) + url = support.match(url, patron=r'<source src="([^"]+)"[^>]+>').match - return support.server(item,url,itemlist) + itemlist.append( + support.Item(channel=item.channel, + action="play", + title='Diretto', + url=url + '|' + support.urllib.urlencode(headers), + server='directo')) + + return support.server(item,itemlist=itemlist) \ No newline at end of file diff --git a/channels/animetubeita.py b/channels/animetubeita.py index 12c5c283..63db64e6 100644 --- a/channels/animetubeita.py +++ b/channels/animetubeita.py @@ -98,11 +98,11 @@ def episodios(item): def findvideos(item): itemlist=[] if item.args == 'last': - match = support.match(item, r'href="(?P<url>[^"]+)"[^>]+><strong>DOWNLOAD & STREAMING</strong>', url=item.url)[0] + match = support.match(item, patron=r'href="(?P<url>[^"]+)"[^>]+><strong>DOWNLOAD & STREAMING</strong>').match if match: patronBlock = r'<h6>Episodio</h6>(?P<block>.*?)(?:<!--|</table>)' patron = r'<a href="http://link\.animetubeita\.com/2361078/(?P<url>[^"]+)"' - match = support.match(item, patron, patronBlock, headers, match[0])[0] + match = support.match(match, patron=patron, patronBlock=patronBlock, headers=headers).match else: return itemlist if match: item.url = match[-1] diff --git a/channels/animeunity.json b/channels/animeunity.json new file mode 100644 index 00000000..99dc0fe0 --- /dev/null +++ b/channels/animeunity.json @@ -0,0 +1,11 @@ +{ + "id": "animeunity", + "name": "AnimeUnity", + "active": true, + "adult": false, + "language": ["ita", "sub-ita"], + "thumbnail": "animeunity.png", + "banner": "animeunity.png", + "categories": ["anime"], + "settings": [] +} diff --git a/channels/animeunity.py b/channels/animeunity.py new file mode 100644 index 00000000..09a628ad --- /dev/null +++ b/channels/animeunity.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------ +# Canale per AnimeUnity +# ------------------------------------------------------------ + +from core import support + +host = support.config.get_channel_url() + +list_servers = ['mixdrop', 'wstream', 'vupplayer', 'supervideo', 'cloudvideo', 'gounlimited'] +list_quality = ['default','1080p', '720p', '480p', '360p'] + +headers = [['Referer', host]] + + +@support.menu +def mainlist(item): + anime = ['/anime.php?c=archive&page=*', + ('In Corso',['/anime.php?c=onair', 'peliculas']), + ('Ultimi Episodi', ['', 'peliculas', 'news']), + ('Ultimi Aggiunti', ['', 'peliculas', 'last']) + ] + return locals() + + +@support.scrape +def menu(item): + action = 'peliculas' + patronBlock = item.args + r' Categorie</a>\s*<ul(?P<block>.*?)</ul>' + patronMenu = r'<a href="(?P<url>[^"]+)"[^>]+>(?P<title>[^>]+)<' + return locals() + + +def search(item, text): + support.log('search', item) + item.url = host + '/anime.php?c=archive&page=*' + item.search = text + try: + return peliculas(item) + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.log('search log:', line) + return [] + + +def newest(categoria): + support.log(categoria) + itemlist = [] + item = support.Item() + item.url = host + item.args = 'news' + item.action = 'peliculas' + try: + itemlist = peliculas(item) + + if itemlist[-1].action == 'peliculas': + itemlist.pop() + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.log({0}.format(line)) + return [] + + return itemlist + + +@support.scrape +def peliculas(item): + # debug = True + pagination = 20 + anime = True + if item.args == 'news': + patron = r'col-lg-3 col-md-6 col-sm-6 col-xs-6 mobile-col">\s*<a href="(?P<url>[^"]+)">[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*<img class="[^"]+" src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^-]+)\D+Episodio\s*(?P<episode>\d+)' + patronNext = r'page-link" href="([^"]+)">' + elif item.args == 'last': + patronBlock = r'ULTIME AGGIUNTE[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<block>.*?)<div class="row"' + patron = r'<img class="[^"]+" src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>\s*<a class="[^"]+" href="(?P<url>[^"]+)"\s*>(?P<title>[^<]+)</a>' + else: + search = item.search + patron = r'<div class="card-img-top archive-card-img"> <a href="(?P<url>[^"]+)"> <img class="[^"]+" src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<\(]+)(?:\((?P<lang>[^\)]+)\))?' + return locals() + + +@support.scrape +def episodios(item): + # debug = True + data = item.data + anime = True + pagination = 50 + patron = r'<a href="(?P<url>[^"]+)" class="\D+ep-button">(?P<episode>\d+)' + def itemHook(item): + item.title = item.title + support.typo(item.fulltitle,'-- bold') + return item + return locals() + + +def findvideos(item): + support.log() + html = support.match(item, patron=r'TIPO:\s*</b>\s*([A-Za-z]+)') + if html.match == 'TV' and item.contentType != 'episode': + item.contentType = 'tvshow' + item.data = html.data + return episodios(item) + else: + itemlist = [] + if item.contentType != 'episode': item.contentType = 'movie' + video = support.match(html.data, patron=r'<source src="([^"]+)"').match + itemlist.append( + support.Item( + channel=item.channel, + action="play", + title='Diretto', + quality='', + url=video, + server='directo', + fulltitle=item.fulltitle, + show=item.show, + contentType=item.contentType, + folder=False)) + return support.server(item, itemlist=itemlist) diff --git a/channels/animeworld.py b/channels/animeworld.py index 4fb56587..e9b26a4c 100644 --- a/channels/animeworld.py +++ b/channels/animeworld.py @@ -10,7 +10,7 @@ headers = [['Referer', host]] __channel__ = 'animeworld' -list_servers = ['animeworld', 'verystream', 'streamango', 'openload', 'directo'] +list_servers = ['directo', 'animeworld', 'vvvvid'] list_quality = ['default', '480p', '720p', '1080p'] @@ -34,7 +34,7 @@ def mainlist(item): def genres(item): support.log() itemlist = [] - matches = support.match(item, r'<input.*?name="([^"]+)" value="([^"]+)"\s*>[^>]+>([^<]+)<\/label>' , r'<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown"> Generi <span.[^>]+>(.*?)</ul>', headers=headers)[0] + matches = support.match(item, patron=r'<input.*?name="([^"]+)" value="([^"]+)"\s*>[^>]+>([^<]+)<\/label>' , patronBlock=r'<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown"> Generi <span.[^>]+>(.*?)</ul>', headers=headers).matches for name, value, title in matches: support.menuItem(itemlist, __channel__, support.typo(title, 'bold'), 'peliculas', host + '/filter?' + name + '=' + value + '&sort=' + order(), 'tvshow', args='sub') return itemlist @@ -44,7 +44,7 @@ def build_menu(item): support.log() itemlist = [] support.menuItem(itemlist, __channel__, 'Tutti bold', 'peliculas', item.url , 'tvshow' , args=item.args) - matches = support.match(item,r'<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown"> (.*?) <span.[^>]+>(.*?)</ul>',r'<form class="filters.*?>(.*?)</form>', headers=headers)[0] + matches = support.match(item, patron=r'<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown"> (.*?) <span.[^>]+>(.*?)</ul>', patronBlock=r'<form class="filters.*?>(.*?)</form>', headers=headers).matches for title, html in matches: if title not in 'Lingua Ordine': support.menuItem(itemlist, __channel__, title + ' submenu bold', 'build_sub_menu', html, 'tvshow', args=item.args) @@ -127,7 +127,7 @@ def peliculas(item): def episodios(item): anime=True pagination = 50 - data = support.match(item, headers=headers)[1] + data = support.match(item, headers=headers).data if 'VVVVID' in data: patronBlock= r'<div class="server\s*active\s*"(?P<block>.*?)</ul>' else: patronBlock= r'server active(?P<block>.*?)server hidden ' patron = r'<li><a [^=]+="[^"]+"[^=]+="[^"]+"[^=]+="[^"]+"[^=]+="[^"]+"[^=]+="[^"]+" href="(?P<url>[^"]+)"[^>]+>(?P<episode>[^<]+)<' @@ -143,9 +143,11 @@ def findvideos(item): import time support.log(item) itemlist = [] - matches, data = support.match(item, r'class="tab.*?data-name="([0-9]+)">', headers=headers) + matches = support.match(item, patron=r'class="tab.*?data-name="([0-9]+)">', headers=headers) + data = matches.data + matches = matches.matches videoData = '' - + for serverid in matches: if not item.number: item.number = support.scrapertools.find_single_match(item.title, r'(\d+) -') block = support.scrapertools.find_multiple_matches(data, 'data-id="' + serverid + '">(.*?)<div class="server') @@ -153,7 +155,7 @@ def findvideos(item): support.log('ID= ',serverid) if id: if serverid == '26': - matches = support.match(item, r'<a href="([^"]+)"', url='%s/ajax/episode/serverPlayer?id=%s' % (host, item.url.split('/')[-1]))[0] + matches = support.match('%s/ajax/episode/serverPlayer?id=%s' % (host, item.url.split('/')[-1]), patron=r'<a href="([^"]+)"', ).matches for url in matches: videoData += '\n' + url else: @@ -162,7 +164,7 @@ def findvideos(item): json = jsontools.load(dataJson) support.log(json) if 'keepsetsu' in json['grabber']: - matches = support.match(item, r'<iframe\s*src="([^"]+)"', url=json['grabber'])[0] + matches = support.match(json['grabber'], patron=r'<iframe\s*src="([^"]+)"'),matches for url in matches: videoData += '\n' + url else: diff --git a/channels/casacinema.py b/channels/casacinema.py index 361aa152..c4046df0 100644 --- a/channels/casacinema.py +++ b/channels/casacinema.py @@ -2,34 +2,12 @@ # ------------------------------------------------------------ # Canale per 'casacinema' # ------------------------------------------------------------ -""" - - Problemi noti che non superano il test del canale: - - Nella ricerca globale non sono presenti le voci: - - "Aggiungi in videoteca" - - "Scarica film/serie" - presenti però quando si entra nella pagina - - Avvisi: - Novità: - - Film, SerieTv - - Ulteriori info: - -""" -import re from core import support -from platformcode import config - -# in caso di necessità -from core import scrapertools, httptools -from core.item import Item -##### fine import -host = config.get_channel_url() +host = support.config.get_channel_url() headers = [['Referer', host]] list_servers = ['verystream', 'openload', 'wstream', 'speedvideo'] @@ -37,9 +15,6 @@ list_quality = ['HD', 'SD'] @support.menu def mainlist(item): - support.log(item) -## support.dbg() - film = ['/category/film', ('Generi', ['', 'genres', 'genres']), ('Sub-ITA', ['/category/sub-ita/', 'peliculas', 'sub']) @@ -53,24 +28,90 @@ def mainlist(item): return locals() + +@support.scrape +def genres(item): + action = 'peliculas' + blacklist = ['PRIME VISIONI', 'ULTIME SERIE TV', 'ULTIMI FILM'] + patronMenu = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<>]+)</a></li>' + patronBlock = r'<div class="container home-cats">(?P<block>.*?)<div class="clear">' + return locals() + + +def select(item): + item.data = support.match(item).data + if 'continua con il video' in item.data.lower(): + support.log('select = ### è un film ###') + item.contentType = 'movie' + return findvideos(item) + else: + support.log('select = ### è una serie ###') + item.contentType = 'tvshow' + return episodios(item) + + +def search(item, text): + support.log(text) + text = text.replace(' ', '+') + item.url = host + '/?s=' + text + item.args = 'search' + try: + item.contentType = '' # non fa uscire le voci nel context menu + return peliculas(item) + + except: + import sys + for line in sys.exc_info(): + support.log('search log:', line) + return [] + + +def newest(categoria): + itemlist = [] + item = support.Item() + item.args = 'newest' + + try: + if categoria == 'series': + item.contentType = 'tvshow' + item.url = host+'/aggiornamenti-serie-tv' + + else: + item.contentType = 'movie' + item.url = host+'/category/film' + + item.action = 'peliculas' + itemlist = peliculas(item) + + if itemlist[-1].action == 'peliculas': + itemlist.pop() + + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.log('newest log: ', {0}.format(line)) + return [] + + return itemlist + + @support.scrape def peliculas(item): - support.log(item) -## support.dbg() # decommentare per attivare web_pdb - if item.contentType == 'movie': action = 'findvideos' elif item.contentType == 'tvshow': action = 'episodios' pagination = '' else: - # è una ricerca action = 'select' - blacklist = [''] - patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div> <div[^>]+>(?P<title>.*?)[ ]?(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?<(?:[^>]+>.+?(?:title="Nuovi episodi">(?P<episode>\d+x\d+)[ ]?(?P<lang2>Sub-Ita)?|title="IMDb">(?P<rating>[^<]+)))?' - patronBlock = r'<h1>.+?</h1>(?P<block>.*?)<aside>' - patronNext = '<a href="([^"]+)" >Pagina' + if item.args == 'newest': + patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div> <div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?<(?:[^>]+>.+?(?:title="Nuovi episodi">(?P<episode>\d+x\d+)[ ]?(?P<lang2>Sub-Ita)?|title="IMDb">(?P<rating>[^<]+)))?' + else: + patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div> <div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?<' + + patronNext = r'<a href="([^"]+)" >Pagina' def itemHook(item): if item.quality1: @@ -82,125 +123,31 @@ def peliculas(item): if item.args == 'novita': item.title = item.title return item - -## debug = True # True per testare le regex sul sito return locals() + @support.scrape def episodios(item): - support.log(item) - #dbg - if item.data1: - data = item.data1 + if item.data: + data = item.data action = 'findvideos' item.contentType = 'tvshow' blacklist = [''] patron = r'(?P<episode>\d+(?:×|×)?\d+\-\d+|\d+(?:×|×)\d+)[;]?(?:(?P<title>[^<]+)<(?P<url>.*?)|(\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 return locals() -# Questa def è utilizzata per generare il menu 'Generi' del canale -# per genere, per anno, per lettera, per qualità ecc ecc -@support.scrape -def genres(item): - support.log(item) - #dbg - - action = 'peliculas' - blacklist = ['PRIME VISIONI', 'ULTIME SERIE TV', 'ULTIMI FILM'] - patron = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<>]+)</a></li>' - patronBlock = r'<div class="container home-cats">(?P<block>.*?)<div class="clear">' - - #debug = True - return locals() - -def select(item): - support.log('select --->', item) -## debug = True - #support.dbg() - data = httptools.downloadpage(item.url, headers=headers).data - data = re.sub('\n|\t', ' ', data) - data = re.sub(r'>\s+<', '> <', data) - if 'continua con il video' in data.lower(): -## block = scrapertools.find_single_match(data, r'<div class="col-md-8 bg-white rounded-left p-5"><div>(.*?)<div style="margin-left: 0.5%; color: #FFF;">') -## if re.findall('rel="category tag">serie', data, re.IGNORECASE): - support.log('select = ### è un film ###') - return findvideos(Item(channel=item.channel, - title=item.title, - fulltitle=item.fulltitle, - url=item.url, - #args='serie', - contentType='movie', - data1 = data - )) - else: - support.log('select = ### è una serie ###') - return episodios(Item(channel=item.channel, - title=item.title, - fulltitle=item.fulltitle, - url=item.url, - #args='serie', - contentType='tvshow', - data1 = data - )) - -############## Fondo Pagina - -def search(item, text): - support.log('search ->', item) - itemlist = [] - text = text.replace(' ', '+') - item.url = host + '/?s=' + text - item.args = 'search' - try: - item.contentType = 'episode' # non fa uscire le voci nel context menu - return peliculas(item) - # Se captura la excepcion, para no interrumpir al buscador global si un canal falla - except: - import sys - for line in sys.exc_info(): - support.log('search log:', line) - return [] - -def newest(categoria): - support.log('newest ->', categoria) - itemlist = [] - item = Item() - - try: - if categoria == 'series': - item.contentType = 'tvshow' - item.url = host+'/aggiornamenti-serie-tv' - item.args = 'novita' - else: - item.contentType = 'movie' - item.url = host+'/category/film' - - item.action = 'peliculas' - itemlist = peliculas(item) - - if itemlist[-1].action == 'peliculas': - itemlist.pop() - # Continua la ricerca in caso di errore - except: - import sys - for line in sys.exc_info(): - support.log('newest log: ', {0}.format(line)) - return [] - - return itemlist def findvideos(item): - support.log('findvideos ->', item) - itemlist = [] if item.contentType != 'movie': - return support.server(item, item.url) + links = support.match(item.url, patron=r'href="([^"]+)"').matches else: - links = str(support.match(item, r'SRC="([^"]+)"', patronBlock=r'<div class="col-md-10">(.+?)<div class="swappable" id="links">')[0]) - if links: - links = links.replace('#', 'speedvideo.net') - return support.server(item, links) - else: - return support.server(item) + matchData = item.data if item.data else item + links = support.match(matchData, patron=r'(?:SRC|href)="([^"]+)"', patronBlock=r'<div class="col-md-10">(.+?)<div class="ads">').matches + data = '' + from lib.unshortenit import unshorten_only + for link in links: + support.log('URL=',link) + url, c = unshorten_only(link.replace('#', 'speedvideo.net')) + data += url + '\n' + return support.server(item, data) diff --git a/channels/cb01anime.py b/channels/cb01anime.py index d08c70b2..9e892298 100644 --- a/channels/cb01anime.py +++ b/channels/cb01anime.py @@ -59,7 +59,7 @@ def peliculas(item): blacklist = Blacklist item.contentType = 'tvshow' if item.args == 'newest': - data = support.match(item)[1] + # data = support.match(item).data patron = r'<div id="blockvids"><ul><li><a href="(?P<url>[^"]+)"[^>]+><img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^\[]+)\[(?P<lang>[^\]]+)\]' else: patron = r'<div class="span4">\s*<a href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)"[^>]+><\/a>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+> <h1>(?P<title>[^<\[]+)(?:\[(?P<lang>[^\]]+)\])?</h1></a>.*?-->(?:.*?<br />)?\s*(?P<plot>[^<]+)' @@ -68,7 +68,7 @@ def peliculas(item): return locals() def check(item): - item.url = support.match(item,r'(?:<p>|/>)(.*?)(?:<br|</td>|</p>)', r'Streaming:(.*?)</tr>')[0] + item.url = support.match(item, patron=r'(?:<p>|/>)(.*?)(?:<br|</td>|</p>)', patronBlock=r'Streaming:(.*?)</tr>').matches if 'Episodio' in str(item.url): item.contentType = 'tvshow' return episodios(item) @@ -87,14 +87,14 @@ def episodios(item): sp = 0 for match in item.url: if 'stagione' in match.lower(): - find_season = support.match(match, r'Stagione\s*(\d+)')[0] - season = int(find_season[0]) if find_season else season + 1 if 'prima' not in match.lower() else season + find_season = support.match(match, patron=r'Stagione\s*(\d+)').match + season = int(find_season) if find_season else season + 1 if 'prima' not in match.lower() else season else: - try: title = support.match(match,'<a[^>]+>([^<]+)</a>')[0][0] + try: title = support.match(match, patron=r'<a[^>]+>([^<]+)</a>').match except: title = '' if title: if 'episodio' in title.lower(): - ep = support.match(match, r'Episodio ((?:\d+.\d|\d+|\D+))')[0][0] + ep = support.match(match, patron=r'Episodio ((?:\d+.\d|\d+|\D+))').match check = ep.isdigit() if check or '.' in ep: if '.' in ep: diff --git a/channels/cineblog01.py b/channels/cineblog01.py index b4c23f11..64a82ac5 100644 --- a/channels/cineblog01.py +++ b/channels/cineblog01.py @@ -22,7 +22,7 @@ def findhost(): host = config.get_channel_url(findhost) headers = [['Referer', host]] -list_servers = ['verystream', 'openload', 'streamango', 'wstream'] +list_servers = ['mixdrop', 'akstream', 'wstream', 'backin'] list_quality = ['HD', 'SD', 'default'] checklinks = config.get_setting('checklinks', 'cineblog01') diff --git a/channels/cinemalibero.py b/channels/cinemalibero.py index 01859ef6..a237bab1 100644 --- a/channels/cinemalibero.py +++ b/channels/cinemalibero.py @@ -12,7 +12,11 @@ from platformcode import config list_servers = ['akstream', 'wstream', 'backin', 'clipwatching', 'cloudvideo', 'verystream', 'onlystream', 'mixdrop'] list_quality = ['default'] -host = config.get_channel_url() +def findhost(): + permUrl = httptools.downloadpage('https://www.cinemalibero.online/', follow_redirects=False).headers + return 'https://www.' + permUrl['location'].replace('https://www.google.com/search?q=site:', '') + +host = config.get_channel_url(findhost) headers = [['Referer', host]] @support.menu diff --git a/channels/cinetecadibologna.py b/channels/cinetecadibologna.py index 892c3a19..9bf10c08 100644 --- a/channels/cinetecadibologna.py +++ b/channels/cinetecadibologna.py @@ -73,7 +73,7 @@ def findvideos(item): support.log() itemlist = [] - matches = support.match(item, 'filename: "(.*?)"')[0] + matches = support.match(item, patron=r'filename: "(.*?)"').matches for url in matches: itemlist.append( diff --git a/channels/dreamsub.json b/channels/dreamsub.json index ba8bf611..d0d39ea9 100644 --- a/channels/dreamsub.json +++ b/channels/dreamsub.json @@ -7,6 +7,5 @@ "thumbnail": "dreamsub.png", "banner": "dreamsub.png", "categories": ["anime", "vos"], - "not_active": ["include_in_newest"], "settings": [] } diff --git a/channels/dreamsub.py b/channels/dreamsub.py index e5ac914c..a9c0889f 100644 --- a/channels/dreamsub.py +++ b/channels/dreamsub.py @@ -2,214 +2,54 @@ # ------------------------------------------------------------ # Canale per 'dreamsub' # ------------------------------------------------------------ -# ------------------------------------------------------------ -""" - - Problemi noti che non superano il test del canale: - - Nessuno noto! - - Avvisi per i tester: - 1. Gli episodi sono divisi per pagine di 20 - 2. In Novità->Anime, cliccare sulla home il bottone "Ultime inserite" - Se avete più titoli in KOD, ridimensiona il browser in modo che si vedano i titoli - a gruppi di 3 e ricontrollare, è un problema del sito. - - 3.Passaggi per Aggiungere in videoteca e/o scaricare Serie: - 1. sul titolo -> menu contestuale -> Rinumerazione - Solo dopo questo passaggio appariranno le voci, sul titolo -> menu contestuale ->: - - Aggiungi in videoteca (senza rinumerazione non appare - la voce) - - Scarica Serie e Scarica Stagione ( Se download Abilitato! ) - - 4. ### PIù IMPORTANTE!!! ### - #### NON E' DA CONSIDERARE ERRORE NEL TEST QUANTO RIPORTATO DI SEGUITO!!!! #### - 1. Il sito permette un filtro tra anime e film, tramite url. - Se nell'url c'è /anime/, sul titolo e proseguendo fino alla pagina del video, saranno - presenti le voci: - - 'Rinumerazione', prima, e dopo: 'Aggiungi in videoteca', 'Scarica Serie' etc... - Tutto il resto è trattato come film e si avranno le voci solite: - AD eccezione per quei "FILM" che hanno 2 o più titoli all'interno, in questo caso: - 1. Non apparirà nessuna voce tra "Aggiungi in videoteca" e "Scarica Film" e nemmeno "rinumerazione" - 2. Dopo essere entrato nella pagina del Titolo Principale, troverai una lista di titoli dove sarà possibile scaricare - il filmato (chiamato EPISODIO) stessa cosa accedendo alla pagina ultima del video - 3. Questi TITOLI NON POSSONO ESSERE AGGIUNTI IN VIDEOTECA - le voci "Scarica FILM" si avranno dopo. - - Es: - https://www.dreamsub.stream/movie/5-centimetri-al-secondo -> film ma ha 3 titoli - - Il Canale NON è presente nelle novità(globale) -> Anime - - -""" -# Qui gli import -import re from core import support -from platformcode import config -from core import scrapertools, httptools, servertools, tmdb -from core.item import Item -##### fine import -host = config.get_channel_url() +host = support.config.get_channel_url() headers = [['Referer', host]] -# server di esempio... -list_servers = ['directo', 'verystream', 'streamango', 'openload'] -# quality di esempio -list_quality = ['default'] - -#### Inizio delle def principali ### - @support.menu def mainlist(item): support.log(item) - anime = ['/anime', -## ('Novità', ['']), -## ('OAV', ['/search/oav', 'peliculas', 'oav']), -## ('OVA', ['/search/ova', 'peliculas', 'ova']), - ('Movie', ['/search/movie', 'peliculas', '', 'movie']), - ('Film', ['/search/film', 'peliculas', '', 'movie']), - ('Categorie', ['/filter?genere=','genres']), -## ('Ultimi Episodi', ['', 'last']) + anime = ['/search?typeY=tv', + ('Movie', ['/search?typeY=movie', 'peliculas', '', 'movie']), + ('OAV', ['/search?typeY=oav', 'peliculas', '', 'tvshow']), + ('Spinoff', ['/search?typeY=spinoff', 'peliculas', '', 'tvshow']), + ('Generi', ['','menu','Generi']), + ('Stato', ['','menu','Stato']), + ('Ultimi Episodi', ['', 'peliculas', ['last', 'episodiRecenti']]), + ('Ultimi Aggiornamenti', ['', 'peliculas', ['last', 'episodiNuovi']]) ] return locals() -@support.scrape -def peliculas(item): - support.log(item) - #dbg # decommentare per attivare web_pdb - - anime = True - if item.args == 'newest': - patronBlock = r'<div class="showRoomGoLeft" sr="ultime"></div>(?P<block>.*?)<div class="showRoomGoRight" sr="ultime">' - else: - patronBlock = r'<input type="submit" value="Vai!" class="blueButton">(?P<block>.*?)<div class="footer">' - -## patron = r'<div class="showStreaming"> <b>(?P<title>[^<]+).+?Stato streaming: '\ -## '(?:[^<]+)<.*?Lingua:[ ](?P<lang1>ITA\/JAP|ITA|JAP)?(?:[ ])?'\ -## '(?P<lang2>SUB ITA)?<br>.+?href="(?P<url>[^"]+)".+?'\ -## 'background: url\((?P<thumb>[^"]+)\).+?<div class="tvTitle">.+?'\ -## '<strong>Anno di inizio</strong>: (?P<year>\d+)<br>' - - patron = r'<div class="showStreaming"> <b>(?P<title>[^<]+).+?Stato streaming: (?:[^<]+)<.*?Lingua:[ ](?P<lang1>ITA\/JAP|ITA|JAP)?(?:[ ])?(?P<lang2>SUB ITA)?<br>.+?href="(?P<url>[^"]+)".+?background: url\((?P<thumb>[^"]+)\).+?<div class="tvTitle">.+?Episodi[^>]+>.\s?(?P<nep>\d+).+?<strong>Anno di inizio</strong>: (?P<year>\d+)<br>' - patronNext = '<li class="currentPage">[^>]+><li[^<]+<a href="([^"]+)">' - - def itemHook(item): - support.log("ITEMHOOK -> ", item) - item = language(item) - - if 'anime' in item.url: - item.contentType = 'tvshow' - item.action = 'episodios' - #item.args = 'anime' - else: - if item.nep == '1': - item.contentType = 'movie' - item.action = 'findvideos' - else: - item.contentType = 'episode' - item.args = '' - item.nep = item.nep - item.action = 'findmovie' - return item - - #debug = True - return locals() @support.scrape -def episodios(item): - support.log(item) - #support.dbg() - - action = 'findvideos' - patronBlock = r'<div class="seasonEp">(?P<block>.*?)<div class="footer">' - patron = r'<li><a href="(?P<url>[^"]+)"[^<]+<b>(?:.+?)[ ](?P<episode>\d+)<\/b>[^>]+>(?P<title>[^<]+)<\/i>[ ]\(?(?P<lang1>ITA|Sub ITA)?\s?.?\s?(?P<lang2>Sub ITA)?.+?\)?<\/a>' - - def itemHook(item): - item = language(item) - return item - - pagination = '' - - #debug = True - return locals() - -@support.scrape -def genres(item): - support.log(item) - #dbg - +def menu(item): item.contentType = '' action = 'peliculas' - blacklist = ['tutti'] - patron = r'<option value="(?P<title>[^"]+)">' - patronBlock = r'<select name="genere" id="genere" class="selectInput">(?P<block>.*?)</select>' + + patronBlock = r'<div class="filter-header"><b>%s</b>(?P<block>.*?)<div class="filter-box">' % item.args + patronMenu = r'<a class="[^"]+" data-state="[^"]+" (?P<other>[^>]+)>[^>]+></i>[^>]+></i>[^>]+></i>(?P<title>[^>]+)</a>' def itemHook(item): - item.contentTitle = item.contentTitle.replace(' ', '+') - item.url = host+'/filter?genere='+item.contentTitle + support.log(item.type) + for Type, ID in support.match(item.other, patron=r'data-type="([^"]+)" data-id="([^"]+)"').matches: + item.url = host + '/search?' + Type + 'Y=' + ID return item - - #debug = True return locals() -@support.scrape -def findmovie(item): - support.log(item) - - patronBlock = r'<div class="seasonEp">(?P<block>.*?)<div class="footer">' - item.contentType = 'episode' - item.nep = 2 - patron = r'<li><a href="(?P<url>[^"]+)"[^>]+>.(?P<title2>.+?)-.+?-[ ]<b>(?P<title>.+?)</b>\s+\(?(?P<lang1>ITA)?\s?(?P<lang2>Sub ITA)?.+?\)?' - - def itemHook(item): - item = language(item) - return item - - #debug = True - return locals() - - -def language(item): - lang = [] - - if item.lang1: - if item.lang1.lower() == 'ita/jap' or item.lang1.lower() == 'ita': - lang.append('ITA') - - if item.lang1.lower() == 'jap' and item.lang1.lower() == 'sub ita': - lang.append('Sub-ITA') - - if item.lang2: - if item.lang2.lower() == 'sub ita': - lang.append('Sub-ITA') - - item.contentLanguage = lang - - if len(lang) ==2: - item.title += support.typo(lang[0], '_ [] color kod') + support.typo(lang[1], '_ [] color kod') - #item.show += support.typo(lang[0], '_ [] color kod') + support.typo(lang[1], '_ [] color kod') - elif len(lang) == 1: - item.title += support.typo(lang[0], '_ [] color kod') - #item.show += support.typo(lang[0], '_ [] color kod') - - return item - - def search(item, text): - support.log('search', item) - itemlist = [] + support.log(text) text = text.replace(' ', '+') item.url = host + '/search/' + text item.args = 'search' try: return peliculas(item) - # Se captura la excepcion, para no interrumpir al buscador global si un canal falla + # Continua la ricerca in caso di errore except: import sys for line in sys.exc_info(): @@ -217,77 +57,88 @@ def search(item, text): return [] -# da adattare... ( support.server ha vari parametri ) -#support.server(item, data='', itemlist=[], headers='', AutoPlay=True, CheckLinks=True) +def newest(categoria): + support.log(categoria) + item = support.Item() + try: + if categoria == "anime": + item.url = host + item.args = ['last', 'episodiNuovi'] + return peliculas(item) + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.logger.error("{0}".format(line)) + return [] + + + +@support.scrape +def peliculas(item): + anime = True + if 'movie' in item.url: + item.contentType = 'movie' + action = 'findvideos' + else: + item.contentType = 'tvshow' + action = 'episodios' + + if len(item.args) > 1 and item.args[0] == 'last': + patronBlock = r'<div id="%s"[^>]+>(?P<block>.*?)<div class="vistaDettagliata"' % item.args[1] + patron = r'<li>\s*<a href="(?P<url>[^"]+)" title="(?P<title>[^"]+)" class="thumb">[^>]+>[^>]+>[^>]+>\s*[EePp]+\s*(?P<episode>\d+)[^>]+>[^>]+>[^>]+>(?P<lang>[^<]*)<[^>]+>[^>]+>\s<img src="(?P<thumb>[^"]+)"' + else: + patron = r'<div class="showStreaming"> <b>(?P<title>[^<]+)[^>]+>[^>]+>\s*Stato streaming: (?:[^<]+)<[^>]+>[^>]+>\s*Lingua:[ ](?P<lang>ITA\/JAP|ITA|JAP|SUB ITA)?[^>]+>[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*<div class="[^"]+" style="background: url\((?P<thumb>[^\)]+)\)' + patronNext = '<li class="currentPage">[^>]+><li[^<]+<a href="([^"]+)">' + + return locals() + + +@support.scrape +def episodios(item): + anime = True + pagination = 100 + + if item.data: + data = item.data + + patron = r'<div class="sli-name">\s*<a href="(?P<url>[^"]+)"[^>]+>(?P<title>[^<]+)<' + + return locals() + + def findvideos(item): - support.log("ITEM ---->", item) itemlist = [] + support.log() - data = httptools.downloadpage(item.url).data - data = re.sub(r'\n|\t', ' ', data) - data = re.sub(r'>\s\s*<', '><', data) - patronBlock = r'LINK STREAMING(?P<block>.*?)LINK DOWNLOAD' - patron = r'href="(.+?)"' - block = scrapertools.find_single_match(data, patronBlock) - urls = scrapertools.find_multiple_matches(block, patron) - #support.regexDbg(item, patron, headers, data=data) + matches = support.match(item, patron=r'<a href="([^"]+)"', patronBlock=r'<div style="white-space: (.*?)<div id="main-content"') - for url in urls: - titles = item.infoLabels['title'] - lang = '' - if 'sub_ita' in url.lower(): - lang = 'Sub-ITA' - else: - lang = 'ITA' + if not matches.matches: + item.data = matches.data + item.contentType = 'tvshow' + return episodios(item) - if 'keepem.online' in data: - urls = scrapertools.find_multiple_matches(data, r'(https://keepem\.online/f/[^"]+)"') - for url in urls: - url = httptools.downloadpage(url).url - itemlist += servertools.find_video_items(data=url) + # matches.matches.sort() - elif 'keepsetsu' in url.lower() or 'woof' in url.lower(): - if 'keepsetsu' in url.lower(): - support.log("keepsetsu url -> ", url ) - data = httptools.downloadpage(url).url - support.log("LINK-DATA :", data) - - data = httptools.downloadpage(data).data - support.log("LINK-DATA2 :", data) - video_urls = scrapertools.find_single_match(data, r'<meta name="description" content="([^"]+)"') - - else: - - data = httptools.downloadpage(url).data - #host_video = scrapertools.find_single_match(data, r'var thisPageUrl = "(http[s]\:\/\/[^\/]+).+?"') - host_video = scrapertools.find_single_match(data, r'(?:let|var) thisPageUrl = "(http[s]\:\/\/[^\/]+).+?"') - link = scrapertools.find_single_match(data, r'<video src="([^"]+)"') - video_urls = host_video+link - - title_show = support.typo(titles,'_ bold') + support.typo(lang,'_ [] color kod') + for url in matches.matches: + lang = url.split('/')[-2] + if 'ita' in lang.lower(): + language = 'ITA' + if 'sub' in lang.lower(): + language = 'Sub-' + language + quality = url.split('/')[-1] itemlist.append( support.Item(channel=item.channel, action="play", contentType=item.contentType, - title=title_show, - fulltitle=item.fulltitle, - show=item.fulltitle, - url=link if 'http' in link else video_urls, - infoLabels = item.infoLabels, - thumbnail=item.thumbnail, - contentSerieName= item.fulltitle, - contentTitle=title_show, - contentLanguage = 'ITA' if lang == [] else lang, - args=item.args, + title=language, + url=url, + contentLanguage = language, + quality = quality, + order = quality.replace('p','').zfill(4), server='directo', )) - if item.contentType != 'episode' and int(item.nep) < 2 : - # Link Aggiungi alla Libreria - if config.get_videolibrary_support() and len(itemlist) > 0 and item.extra != 'findservers': - support.videolibrary(itemlist, item) - # link per scaricare - if config.get_setting('downloadenabled'): - support.download(itemlist, item) - return itemlist + itemlist.sort(key=lambda x: (x.title, x.order), reverse=False) + return support.server(item, itemlist=itemlist) \ No newline at end of file diff --git a/channels/fastsubita.py b/channels/fastsubita.py index b022c94e..b96af5e8 100644 --- a/channels/fastsubita.py +++ b/channels/fastsubita.py @@ -145,7 +145,9 @@ def findvideos(item): itemlist = [] patronBlock = '<div class="entry-content">(?P<block>.*)<footer class="entry-footer">' patron = r'<a href="([^"]+)">' - matches, data = support.match(item, patron, patronBlock, headers) + html = support.match(item, patron=patron, patronBlock=patronBlock, headers=headers) + matches = html.matches + data= html.data if item.args != 'episodios': item.infoLabels['mediatype'] = 'episode' @@ -156,7 +158,7 @@ def findvideos(item): itemlist += support.server(item, data) - data = httptools.downloadpage(item.url).data + data = support.match(item.url).data patron = r'>Posted in <a href="https?://fastsubita.com/serietv/([^/]+)/(?:[^"]+)?"' series = scrapertools.find_single_match(data, patron) titles = support.typo(series.upper().replace('-', ' '), 'bold color kod') diff --git a/channels/filmpertutti.py b/channels/filmpertutti.py index 33fc088b..ca967ebf 100644 --- a/channels/filmpertutti.py +++ b/channels/filmpertutti.py @@ -2,19 +2,7 @@ # ------------------------------------------------------------ # Canale per filmpertutti.py # ------------------------------------------------------------ -""" - Questi sono commenti per i beta-tester. - Su questo canale, nella categoria 'Ricerca Globale' - non saranno presenti le voci 'Aggiungi alla Videoteca' - e 'Scarica Film'/'Scarica Serie', dunque, - la loro assenza, nel Test, NON dovrà essere segnalata come ERRORE. - - Novità (globale). Indicare in quale/i sezione/i è presente il canale: - - film, serie - - I titoli in questa sezione a gruppi di 20 - -""" import re from core import scrapertools, httptools, support @@ -24,7 +12,7 @@ from platformcode import config host = config.get_channel_url() headers = [['Referer', host]] -list_servers = ['speedvideo', 'verystream', 'openload', 'streamango', 'wstream', 'akvideo'] +list_servers = ['mixdrop', 'akvideo', 'wstream', 'onlystream', 'speedvideo'] list_quality = ['HD', 'SD'] @support.menu @@ -74,16 +62,11 @@ def peliculas(item): @support.scrape def episodios(item): - data = httptools.downloadpage(item.url, headers=headers).data - data = re.sub('\n|\t', ' ', data) - data = re.sub(r'>\s+<', '> <', data) - support.log('SERIES DATA= ',data) + data = support.match(item.url, headers=headers).data if 'accordion-item' in data: patronBlock = r'<span class="season[^>]*>\d+[^>]+>[^>]+>[^>]+>[^>]+>\D*(?:STAGIONE|Stagione)[ -]+(?P<lang>[a-zA-Z\- ]+)[^<]*</span>(?P<block>.*?)<div id="(?:season|disqus)' - patron = r'<img src="(?P<thumb>[^"]+)">.*?<li class="season-no">(?P<episode>[^<]+)<\/li>(?P<url>.*?javascript:;">)(?P<title>[^<]+)' + patron = r'<img src="(?P<thumb>[^"]+)">.*?<li class="season-no">(?P<episode>[^<]+)<\/li>(?P<url>.*?javascript:;">(?P<title>[^<]+).*?</tbody>)' else: - # patronBlock = r'<div id="info" class="pad">(?P<block>.*?)<div id="disqus_thread">' - # deflang='Sub-ITA' patronBlock = r'(?:STAGIONE|Stagione)(?:<[^>]+>)?\s*(?:(?P<lang>[A-Za-z- ]+))?(?P<block>.*?)(?: |<strong>|<div class="addtoany)' patron = r'(?:/>|p>)\s*(?P<season>\d+)(?:×|×|x)(?P<episode>\d+)[^<]+(?P<url>.*?)(?:<br|</p)' def itemHook(item): @@ -104,17 +87,16 @@ def genres(item): action = 'peliculas' patronBlock = r'<select class="cats">(?P<block>.*?)<\/select>' - patronMenu = r'<option data-src="(?P<url>[^"]+)">(?P<title>.*?)<\/option>' + patronMenu = r'<option data-src="(?P<url>[^"]+)">(?P<title>[^<]+)<\/option>' return locals() def select(item): support.log() - - data = httptools.downloadpage(item.url, headers=headers).data - patronBlock = scrapertools.find_single_match(data, r'class="taxonomy category" ><span property="name">(.*?)</span></a><meta property="position" content="2">') - if patronBlock.lower() != 'film': + patron=r'class="taxonomy category" ><span property="name">([^>]+)</span></a><meta property="position" content="2">' + block = support.match(item.url, patron=patron,headers=headers).match + if block.lower() != 'film': support.log('select = ### è una serie ###') item.contentType='tvshow' return episodios(item) diff --git a/channels/ilgeniodellostreaming.py b/channels/ilgeniodellostreaming.py index 3cbb560e..2c7fb457 100644 --- a/channels/ilgeniodellostreaming.py +++ b/channels/ilgeniodellostreaming.py @@ -213,7 +213,9 @@ def findvideos(item): log() itemlist =[] - matches, data = support.match(item, '<iframe class="metaframe rptss" src="([^"]+)"[^>]+>',headers=headers) + html = support.match(item, patron='<iframe class="metaframe rptss" src="([^"]+)"[^>]+>',headers=headers) + matches = html.matches + data = html.data for url in matches: html = httptools.downloadpage(url, headers=headers).data data += str(scrapertools.find_multiple_matches(html, '<meta name="og:url" content="([^"]+)">')) @@ -224,7 +226,7 @@ def findvideos(item): data = httptools.downloadpage(item.url).data patron = r'<div class="item"><a href="'+host+'/serietv/([^"\/]+)\/"><i class="icon-bars">' - series = scrapertools.find_single_match(data, patron) + series = support.match(data, patron=patron).matches titles = support.typo(series.upper().replace('-', ' '), 'bold color kod') goseries = support.typo("Vai alla Serie:", ' bold') itemlist.append( diff --git a/channels/italiaserie.json b/channels/italiaserie.json index fe68c5c1..a8d184b6 100644 --- a/channels/italiaserie.json +++ b/channels/italiaserie.json @@ -4,8 +4,8 @@ "active": true, "adult": false, "language": ["ita","sub-ita"], - "thumbnail": "https:\/\/raw.githubusercontent.com\/Zanzibar82\/images\/master\/posters\/italiaserie.png", - "bannermenu": "https:\/\/raw.githubusercontent.com\/Zanzibar82\/images\/master\/posters\/italiaserie.png", + "thumbnail": "italiaserie.png", + "bannermenu": "italiaserie.png", "categories": ["tvshow", "vos"], "not_active": ["include_in_newest_peliculas", "include_in_newest_anime"], "settings": [] diff --git a/channels/piratestreaming.py b/channels/piratestreaming.py index c8b797a3..92aa9418 100644 --- a/channels/piratestreaming.py +++ b/channels/piratestreaming.py @@ -99,7 +99,7 @@ def findvideos(item): if item.contentType == 'episode': data = item.url else: - data = support.match(item)[1] + data = support.match(item).data if 'link-episode' in data: item.data = data return episodios(item) diff --git a/channels/pufimovies.json b/channels/pufimovies.json new file mode 100644 index 00000000..5ded05f3 --- /dev/null +++ b/channels/pufimovies.json @@ -0,0 +1,11 @@ +{ + "id": "pufimovies", + "name": "PufiMovies", + "active": true, + "adult": false, + "language": ["ita", "sub-ita"], + "thumbnail": "pufimovies.png", + "banner": "pufimovies.png", + "categories": ["movie","tvshow"], + "settings": [] +} diff --git a/channels/pufimovies.py b/channels/pufimovies.py new file mode 100644 index 00000000..9600c9fe --- /dev/null +++ b/channels/pufimovies.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------ +# Canale per PufiMovies +# ------------------------------------------------------------ + +from core import support + +host = support.config.get_channel_url() + +list_servers = ['mixdrop', 'wstream', 'vupplayer', 'supervideo', 'cloudvideo', 'gounlimited'] +list_quality = ['default','1080p', '720p', '480p', '360p'] + +headers = [['Referer', host]] + + +@support.menu +def mainlist(item): + film = [ + ('Generi', ['', 'menu', 'Film']), + ('Più Visti', ['','peliculas', 'most']) + ] + + tvshow = ['', + ('Generi', ['', 'menu', 'Serie Tv']), + ('Ultimi Episodi', ['','peliculas', 'last']) + ] + + search = '' + return locals() + + +@support.scrape +def menu(item): + action = 'peliculas' + patronBlock = item.args + r' Categorie</a>\s*<ul(?P<block>.*?)</ul>' + patronMenu = r'<a href="(?P<url>[^"]+)"[^>]+>(?P<title>[^>]+)<' + return locals() + + +def search(item, text): + support.log('search', item) + + text = text.replace(' ', '+') + item.url = host + '/search/keyword/' + text + try: + item.args = 'search' + return peliculas(item) + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.log('search log:', line) + return [] + + +def newest(categoria): + support.log(categoria) + itemlist = [] + item = support.Item() + item.url = host + item.action = 'peliculas' + try: + if categoria == 'peliculas': + item.contentType = 'movie' + itemlist = peliculas(item) + else: + item.args = 'last' + item.contentType = 'tvshow' + itemlist = peliculas(item) + + if itemlist[-1].action == 'peliculas': + itemlist.pop() + # Continua la ricerca in caso di errore + except: + import sys + for line in sys.exc_info(): + support.log({0}.format(line)) + return [] + + return itemlist + + +@support.scrape +def peliculas(item): + if item.contentType == 'tvshow' and not item.args: + action = 'episodios' + patron = r'<div class="movie-box">\s*<a href="(?P<url>[^"]+)">[^>]+>[^>]+>\D+Streaming\s(?P<lang>[^"]+)[^>]+>[^>]+>[^>]+>(?P<quality>[^<]+)[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>(?P<rating>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>\s*(?P<year>\d+)' + elif item.contentType == 'movie' and not item.args: + patron = r'<div class="existing_item col-6 col-lg-3 col-sm-4 col-xl-4">\s*<div class="movie-box">\s*<a href="(?P<url>(?:http(?:s)://[^/]+)?/(?P<type>[^/]+)/[^"]+)">[^>]+>[^>]+>\D+Streaming\s*(?P<lang>[^"]+)">[^>]+>[^>]+>(?P<quality>[^<]+)<[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>(?P<rating>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>\s*(?:(?P<year>\d+))?[^>]+>[^>]+>[^>]+>[^>]+>(?P<plot>[^<]*)<' + elif item.args == 'last': + patron = r'<div class="episode-box">[^>]+>[^>]+>[^>]+>\D+Streaming\s(?P<lang>[^"]+)">[^>]+>[^>]+>(?P<quality>[^<]+)<[^>]+>[^>]+>[^>]+>[^^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>[^>]+>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>\D*(?P<season>\d+)[^>]+>\D*(?P<episode>\d+)' + elif item.args == 'most': + patron =r'div class="sm-113 item">\s*<a href="(?P<url>[^"]+)">[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<title>[^<]+)' + else: + patron = r'<div class="movie-box">\s*<a href="(?P<url>(?:http(?:s)://[^/]+)?/(?P<type>[^/]+)/[^"]+)">[^>]+>[^>]+>\D+Streaming\s*(?P<lang>[^"]+)">[^>]+>[^>]+>(?P<quality>[^<]+)<[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>(?P<rating>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>\s*(?:(?P<year>\d+))?[^>]+>[^>]+>[^>]+>[^>]+>(?P<plot>[^<]*)<' + typeActionDict = {'findvideos':['movie'], 'episodios':['tvshow']} + typeContentDict = {'movie':['movie'], 'tvshow':['tvshow']} + patronNext = r'<a href="([^"]+)"[^>]+>»' + return locals() + + +@support.scrape +def episodios(item): + patron = r'<div class="episode-box">[^>]+>[^>]+>[^>]+>\D+Streaming\s(?P<lang>[^"]+)">[^>]+>[^>]+>(?P<quality>[^<]+)<[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>[^>]+>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>\D*(?P<season>\d+)[^>]+>\D*(?P<episode>\d+)' + return locals() + + +def findvideos(item): + support.log() + # match = support.match(item, patron='wstream', debug=True) + return support.server(item) diff --git a/channels/seriehd.py b/channels/seriehd.py index a60c691e..95536858 100644 --- a/channels/seriehd.py +++ b/channels/seriehd.py @@ -34,11 +34,11 @@ def peliculas(item): @support.scrape def episodios(item): data ='' - url = support.match(item, patronBlock=r'<iframe width=".+?" height=".+?" src="([^"]+)" allowfullscreen frameborder="0">')[1] - seasons = support.match(item, r'<a href="([^"]+)">(\d+)<', r'<h3>STAGIONE</h3><ul>(.*?)</ul>', headers, url)[0] + url = support.match(item, patron=r'<iframe width=".+?" height=".+?" src="([^"]+)" allowfullscreen frameborder="0">').match + seasons = support.match(url, patron=r'<a href="([^"]+)">(\d+)<', patronBlock=r'<h3>STAGIONE</h3><ul>(.*?)</ul>', headers=headers).matches for season_url, season in seasons: season_url = support.urlparse.urljoin(url, season_url) - episodes = support.match(item, r'<a href="([^"]+)">(\d+)<', '<h3>EPISODIO</h3><ul>(.*?)</ul>', headers, season_url)[0] + episodes = support.match(season_url, patron=r'<a href="([^"]+)">(\d+)<', patronBlock=r'<h3>EPISODIO</h3><ul>(.*?)</ul>', headers=headers).matches for episode_url, episode in episodes: episode_url = support.urlparse.urljoin(url, episode_url) title = season + "x" + episode.zfill(2) + ' - ' + item.fulltitle diff --git a/channels/serietvsubita.py b/channels/serietvsubita.py index 1345fffa..47cb8850 100644 --- a/channels/serietvsubita.py +++ b/channels/serietvsubita.py @@ -108,7 +108,7 @@ def lista_serie(item): else: # Extrae las entradas patron = r'<li class="cat-item cat-item-\d+"><a href="([^"]+)"\s?>([^<]+)</a>' - matches = support.match(item, patron, headers=headers)[0] + matches = support.match(item, patron=patron, headers=headers).matches for i, (scrapedurl, scrapedtitle) in enumerate(matches): scrapedplot = "" scrapedthumbnail = "" @@ -148,7 +148,9 @@ def episodios(item, itemlist=[]): patron += r'<p><a href="([^"]+)">' - matches, data = support.match(item, patron, headers=headers) + html = support.match(item, patron=patron, headers=headers) + matches = html.matches + data = html.data for scrapedurl, scrapedtitle, scrapedthumbnail in matches: scrapedplot = "" @@ -224,7 +226,9 @@ def peliculas_tv(item): patron = '<div class="post-meta">\s*<a href="([^"]+)"\s*title="([^"]+)"\s*class=".*?"></a>' - matches, data = support.match(item, patron, headers=headers) + html = support.match(item, patron=patron, headers=headers) + matches = html.matches + data = html.data for scrapedurl, scrapedtitle in matches: if scrapedtitle in ["FACEBOOK", "RAPIDGATOR", "WELCOME!"]: @@ -298,7 +302,7 @@ def search(item, texto): itemlist = [] patron = '<li class="cat-item cat-item-\d+"><a href="([^"]+)"\s?>([^<]+)</a>' - matches = support.match(item, patron, headers=headers)[0] + matches = support.match(item, patron=patron, headers=headers).matches for i, (scrapedurl, scrapedtitle) in enumerate(matches): if texto.upper() in scrapedtitle.upper(): scrapedthumbnail = "" @@ -333,7 +337,7 @@ def list_az(item): alphabet = dict() patron = '<li class="cat-item cat-item-\d+"><a href="([^"]+)"\s?>([^<]+)</a>' - matches = support.match(item, patron, headers=headers)[0] + matches = support.match(item, patron=patron, headers=headers).matches for i, (scrapedurl, scrapedtitle) in enumerate(matches): letter = scrapedtitle[0].upper() if letter not in alphabet: diff --git a/channels/serietvu.py b/channels/serietvu.py index c4c9bd2f..9c9a92db 100644 --- a/channels/serietvu.py +++ b/channels/serietvu.py @@ -35,7 +35,6 @@ def mainlist(item): def peliculas(item): patronBlock = r'<div class="wrap">\s*<h.>.*?</h.>(?P<block>.*?)<footer>' - if item.args != 'update': action = 'episodios' patron = r'<div class="item">\s*<a href="(?P<url>[^"]+)" data-original="(?P<thumb>[^"]+)" class="lazy inner">[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<]+)<' @@ -50,14 +49,15 @@ def peliculas(item): @support.scrape def episodios(item): - seasons, data = support.match(item, r'<option value="(\d+)"[^>]*>\D+(\d+)') + seasons = support.match(item, patron=r'<option value="(\d+)"[^>]*>\D+(\d+)').matches patronBlock = r'</select><div style="clear:both"></div></h2>(?P<block>.*?)<div id="trailer" class="tab">' - patron = r'(?:<div class="list (?:active)?" data-id="(?P<season>\d+)">[^>]+>)?\s*<a data-id="(?P<episode>\d+)(?:[ ](?P<lang>[SuUbBiItTaA\-]+))?"(?P<url>[^>]+)>[^>]+>[^>]+>(?P<title>.+?)(?:\sSub-ITA)?<' + patron = r'(?:<div class="list (?:active)?")?\s*<a data-id="\d+(?:[ ](?P<lang>[SuUbBiItTaA\-]+))?"(?P<other>[^>]+)>.*?Episodio [0-9]+\s?(?:<br>(?P<title>[^<]+))?.*?Stagione (?P<season>[0-9]+) , Episodio - (?P<episode>[0-9]+).*?<(?P<url>.*?<iframe)' def itemHook(item): for value, season in seasons: log(value) log(season) item.title = item.title.replace(value+'x',season+'x') + item.url += '\n' + item.other return item return locals() @@ -110,7 +110,11 @@ def newest(categoria): def findvideos(item): log() if item.args != 'update': - return support.server(item, data=item.url) + data = item.url + toUnshorten = scrapertools.find_multiple_matches(data, 'https?://buckler.link/[a-zA-Z0-9]+') + for link in toUnshorten: + data += '\n' + httptools.downloadpage(link, follow_redirects=False).headers["Location"] + return support.server(item, data=data) else: itemlist = [] item.infoLabels['mediatype'] = 'episode' diff --git a/channels/streamtime.py b/channels/streamtime.py index 3aad1040..e32056f3 100644 --- a/channels/streamtime.py +++ b/channels/streamtime.py @@ -149,15 +149,15 @@ def findvideos(item): id = item.args['id'] season = str(item.args['season']) episode = str(item.args['episode']) - res = support.match(item, 'src="([^"]+)"[^>]*></video>', url=url, headers=[['Referer', domain]]) + res = support.match(url, patron='src="([^"]+)"[^>]*></video>', headers=[['Referer', domain]]).match itemlist = [] - if res[0]: + if res: itemlist.append( Item(channel=item.channel, action="play", title='contentful', - url=res[0][0], + url=res, server='directo', fulltitle=item.fulltitle, thumbnail=item.thumbnail, diff --git a/channels/tantifilm.py b/channels/tantifilm.py index 9b17b2d8..6582b3d9 100644 --- a/channels/tantifilm.py +++ b/channels/tantifilm.py @@ -124,7 +124,7 @@ def anime(item): log() itemlist = [] - seasons = support.match(item, r'<div class="sp-body[^"]+">(.*?)<\/div>')[0] + seasons = support.match(item, patron=r'<div class="sp-body[^"]+">(.*?)<\/div>').matches for season in seasons: episodes = scrapertools.find_multiple_matches(season, r'<a.*?href="([^"]+)"[^>]+>([^<]+)<\/a>(.*?)<(:?br|\/p)') for url, title, urls, none in episodes: @@ -208,7 +208,7 @@ def newest(categoria): item = Item() item.url = host +'/aggiornamenti/' - matches = support.match(item, r'mediaWrapAlt recomended_videos"[^>]+>\s*<a href="([^"]+)" title="([^"]+)" rel="bookmark">\s*<img[^s]+src="([^"]+)"[^>]+>')[0] + matches = support.match(item, patron=r'mediaWrapAlt recomended_videos"[^>]+>\s*<a href="([^"]+)" title="([^"]+)" rel="bookmark">\s*<img[^s]+src="([^"]+)"[^>]+>').matches for url, title, thumb in matches: title = scrapertools.decodeHtmlentities(title).replace("Permalink to ", "").replace("streaming", "") @@ -236,11 +236,11 @@ def findvideos(item): ## data = item.url ## else: ## data = httptools.downloadpage(item.url, headers=headers).data - data = httptools.downloadpage(item.url, headers=headers).data + data = support.match(item.url, headers=headers).data data = re.sub('\n|\t', ' ', data) data = re.sub(r'>\s+<', '> <', data) - check = scrapertools.find_single_match(data, r'<div class="category-film">\s+<h3>\s+(.*?)\s+</h3>\s+</div>') + check = support.match(data, patron=r'<div class="category-film">\s+<h3>\s+(.*?)\s+</h3>\s+</div>').match if 'sub' in check.lower(): item.contentLanguage = 'Sub-ITA' support.log("CHECK : ", check) diff --git a/channels/toonitalia.py b/channels/toonitalia.py index 7736f26d..5f6dbef2 100644 --- a/channels/toonitalia.py +++ b/channels/toonitalia.py @@ -104,7 +104,7 @@ def peliculas(item): @support.scrape def episodios(item): anime = True - data = support.match(item, headers=headers)[1] + data = support.match(item, headers=headers).data if 'https://vcrypt.net' in data: patron = r'(?:<br /> |<p>)(?P<title>[^<]+)<a href="(?P<url>[^"]+)"' else: diff --git a/channels/vedohd.json b/channels/vedohd.json index 2109a2b1..e21ccead 100644 --- a/channels/vedohd.json +++ b/channels/vedohd.json @@ -4,33 +4,8 @@ "language": ["ita"], "active": true, "adult": false, - "thumbnail": "https://vedohd.pw/wp-content/uploads/2018/12/imgpsh_fullsize.png", - "banner": "https://vedohd.pw/wp-content/uploads/2018/12/imgpsh_fullsize.png", + "thumbnail": "vedohd.png", + "banner": "vedohd.png", "categories": ["movie"], - "settings": [ - { - "id": "include_in_global_search", - "type": "bool", - "label": "Includi in Ricerca Globale", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "include_in_newest_peliculas", - "type": "bool", - "label": "Includi in Novità - Film", - "default": true, - "enabled": true, - "visible": true - }, - { - "id": "include_in_newest_italiano", - "type": "bool", - "label": "Includi in Novità - Italiano", - "default": true, - "enabled": true, - "visible": true - } - ] + "settings": [] } diff --git a/channels/vvvvid.py b/channels/vvvvid.py index 7c4975ef..02e8dce1 100644 --- a/channels/vvvvid.py +++ b/channels/vvvvid.py @@ -131,7 +131,7 @@ def peliculas(item): elif '=' in item.args: json_file = current_session.get(item.url + 'channels', headers=headers, params=payload).json() - Filter = support.match(item.args,r'\?([^=]+)=')[0][0] + Filter = support.match(item.args, patron=r'\?([^=]+)=').match keys = [i[Filter] for i in json_file['data'] if Filter in i][0] for key in keys: if key not in ['1','2']: @@ -162,7 +162,7 @@ def episodios(item): for episode in episodes: for key in episode: if 'stagione' in key['title'].encode('utf8').lower(): - match = support.match(key['title'].encode('utf8'), r'[Ss]tagione\s*(\d+) - [Ee]pisodio\s*(\d+)')[0][0] + match = support.match(key['title'].encode('utf8'), patron=r'[Ss]tagione\s*(\d+) - [Ee]pisodio\s*(\d+)').match title = match[0]+'x'+match[1] + ' - ' + item.fulltitle make_item = True elif int(key['season_id']) == int(season_id): @@ -206,7 +206,7 @@ def findvideos(item): if 'youtube' in url: item.url = url item.url = url.replace('manifest.f4m','master.m3u8').replace('http://','https://').replace('/z/','/i/') if 'https' not in item.url: - url = support.match(item, url='https://or01.top-ix.org/videomg/_definst_/mp4:' + item.url + '/playlist.m3u')[1] + url = support.match('https://or01.top-ix.org/videomg/_definst_/mp4:' + item.url + '/playlist.m3u') url = url.split()[-1] itemlist.append( Item(action= 'play', diff --git a/channelselector.py b/channelselector.py index 080c73c8..ebc672a2 100644 --- a/channelselector.py +++ b/channelselector.py @@ -313,8 +313,8 @@ def set_channel_info(parameters): else: content = config.get_localized_category(cat) - info = '[COLOR yellow]' + config.get_localized_string(70567) + ' [/COLOR]' + content + '\n\n' - info += '[COLOR yellow]' + config.get_localized_string(70568) + ' [/COLOR] ' + language + info = '[B]' + config.get_localized_string(70567) + ' [/B]' + content + '\n\n' + info += '[B]' + config.get_localized_string(70568) + ' [/B] ' + language return info diff --git a/core/scraper.py b/core/scraper.py index 73bef114..5d3c9f95 100644 --- a/core/scraper.py +++ b/core/scraper.py @@ -123,7 +123,7 @@ def cuadro_completar(item): global dict_default dict_default = {} - COLOR = ["0xFF8A4B08", "0xFFF7BE81"] + COLOR = ["0xFF65B3DA", "0xFFFFFFFF"] # Creamos la lista de campos del infoLabel controls = [("title", "text", config.get_localized_string(60230)), ("originaltitle", "text", config.get_localized_string(60231)), diff --git a/core/support.py b/core/support.py index b706e3de..dc44f8ab 100755 --- a/core/support.py +++ b/core/support.py @@ -180,7 +180,7 @@ def scrapeBlock(item, args, block, patron, headers, action, pagination, debug, t if debug: regexDbg(item, patron, headers, block) - known_keys = ['url', 'title', 'title2', 'season', 'episode', 'thumb', 'quality', 'year', 'plot', 'duration', 'genere', 'rating', 'type', 'lang'] + known_keys = ['url', 'title', 'title2', 'season', 'episode', 'thumb', 'quality', 'year', 'plot', 'duration', 'genere', 'rating', 'type', 'lang', 'other'] # Legenda known_keys per i groups nei patron # known_keys = ['url', 'title', 'title2', 'season', 'episode', 'thumb', 'quality', # 'year', 'plot', 'duration', 'genere', 'rating', 'type', 'lang'] @@ -201,8 +201,8 @@ def scrapeBlock(item, args, block, patron, headers, action, pagination, debug, t stagione = '' # per quei siti che hanno la stagione nel blocco ma non nelle puntate for i, match in enumerate(matches): - if pagination and (pag - 1) * pagination > i: continue # pagination - if pagination and i >= pag * pagination: break # pagination + if pagination and (pag - 1) * pagination > i and not search: continue # pagination + if pagination and i >= pag * pagination and not search: break # pagination listGroups = match.keys() match = match.values() @@ -214,12 +214,12 @@ def scrapeBlock(item, args, block, patron, headers, action, pagination, debug, t for kk in known_keys: val = match[listGroups.index(kk)] if kk in listGroups else '' if val and (kk == "url" or kk == 'thumb') and 'http' not in val: - val = scrapertools.find_single_match(item.url, 'https?://[a-z0-9.-]+') + val + val = scrapertools.find_single_match(item.url, 'https?://[a-z0-9.-]+') + (val if val.startswith('/') else '/' + val) scraped[kk] = val if scraped['season']: stagione = scraped['season'] - episode = scraped['season'] +'x'+ scraped['episode'] + episode = scraped['season'] +'x'+ scraped['episode'].zfill(2) elif item.season: episode = item.season +'x'+ scraped['episode'] elif item.contentType == 'tvshow' and (scraped['episode'] == '' and scraped['season'] == '' and stagione == ''): @@ -295,13 +295,14 @@ def scrapeBlock(item, args, block, patron, headers, action, pagination, debug, t quality=quality, url=scraped["url"], infoLabels=infolabels, - thumbnail=item.thumbnail if function == 'episodios' else scraped["thumb"] , + thumbnail=item.thumbnail if function == 'episodios' and not scraped["thumb"] else scraped["thumb"] , args=item.args, contentSerieName= scraped['title'] if item.contentType or CT != 'movie' and function != 'episodios' else item.fulltitle if function == 'episodios' else '', contentTitle= scraped['title'] if item.contentType or CT == 'movie' else '', contentLanguage = lang1, contentEpisodeNumber=episode if episode else '', - news= item.news if item.news else '' + news= item.news if item.news else '', + other = scraped['other'] if scraped['other'] else '' ) for lg in list(set(listGroups).difference(known_keys)): @@ -445,7 +446,7 @@ def scrape(func): if anime: if function == 'episodios' or item.action == 'episodios': autorenumber.renumber(itemlist, item, 'bold') else: autorenumber.renumber(itemlist) - if anime and autorenumber.check(item) == False and not scrapertools.find_single_match(itemlist[0].title, r'(\d+.\d+)'): + if anime and autorenumber.check(item) == False and len(itemlist)>0 and not scrapertools.find_single_match(itemlist[0].title, r'(\d+.\d+)'): pass else: if addVideolibrary and (item.infoLabels["title"] or item.fulltitle): @@ -637,8 +638,9 @@ def menu(func): item = args['item'] host = func.__globals__['host'] - list_servers = func.__globals__['list_servers'] if 'list_servers' in func.__globals__ else 'directo' - list_quality = func.__globals__['list_quality'] if 'list_quality' in func.__globals__ else 'default' + list_servers = func.__globals__['list_servers'] if 'list_servers' in func.__globals__ else ['directo'] + list_quality = func.__globals__['list_quality'] if 'list_quality' in func.__globals__ else ['default'] + log('LIST QUALITY', list_quality) filename = func.__module__.split('.')[1] global_search = False # listUrls = ['film', 'filmSub', 'tvshow', 'tvshowSub', 'anime', 'animeSub', 'search', 'top', 'topSub'] @@ -772,31 +774,91 @@ def typo(string, typography=''): return string -def match(item, patron='', patronBlock='', headers='', url='', post=''): +def match(item_url_string, **args): + ''' + match is a function that combines httptools and scraper tools: + ''' + log(item_url_string) + matches = [] - if type(item) == str: - data = item + url = None + # arguments allowed for scrape + patron = args.get('patron', None) + patronBlock = args.get('patronBlock', None) + patronBlocks = args.get('patronBlock', None) + debug = args.get('debug', False) + debugBlock = args.get('debugBlock', False) + string = args.get('string', False) + # remove scrape arguments + args = dict([(key, val) for key, val in args.items() if key not in ['patron', 'patronBlock', 'patronBlocks', 'debug', 'debugBlock', 'string']]) + # dbg() + # check type of item_url_string + if type(item_url_string) == str: + if item_url_string.startswith('http') and not string: url = item_url_string + else : data = item_url_string else: - url = url if url else item.url - if post: - data = httptools.downloadpage(url, headers=headers, ignore_response_code=True, post=post).data.replace("'", '"') - else: - data = httptools.downloadpage(url, headers=headers, ignore_response_code=True).data.replace("'", '"') + # if item_url_string is an item use item.url as url + url = item_url_string.url + + # if there is a url, download the page + if url: + if args.get('ignore_response_code', None) is None: + args['ignore_response_code'] = True + data = httptools.downloadpage(url, **args).data.replace("'", '"') + + # format page data data = re.sub(r'\n|\t', ' ', data) data = re.sub(r'>\s\s*<', '><', data) - log('DATA= ', data) + # collect blocks of a page if patronBlock: - block = scrapertools.find_single_match(data, patronBlock) - log('BLOCK= ',block) + blocks = [scrapertools.find_single_match(data, patronBlock)] + elif patronBlocks: + blocks = scrapertools.find_multiple_matches(data, patronBlock) else: - block = data + blocks = [data] + # match if patron: - matches = scrapertools.find_multiple_matches(block, patron) - log('MATCHES= ',matches) + if type(patron) == str: patron = [patron] + for b in blocks: + for p in patron: + matches += scrapertools.find_multiple_matches(b, p) - return matches, block + # debug mode + if config.dev_mode(): + if debugBlock: + match_dbg(data, patronBlock) + if debug: + for block in blocks: + for p in patron: + match_dbg(block, p) + + # create a item + item = Item(data=data, + blocks=blocks, + block=blocks[0] if len(blocks) > 0 else '', + matches=matches, + match=matches[0] if len(matches) > 0 else '') + + return item + + +def match_dbg(data, patron): + import json, urllib2, webbrowser + url = 'https://regex101.com' + headers = {'content-type': 'application/json'} + data = { + 'regex': patron, + 'flags': 'gm', + 'testString': data, + 'delimiter': '"""', + 'flavor': 'python' + } + r = urllib2.Request(url + '/api/regex', json.dumps(data, encoding='latin1'), headers=headers) + r = urllib2.urlopen(r).read() + permaLink = json.loads(r)['permalinkFragment'] + webbrowser.open(url + "/r/" + permaLink) def download(itemlist, item, typography='', function_level=1, function=''): @@ -981,7 +1043,7 @@ def controls(itemlist, item, AutoPlay=True, CheckLinks=True, down_load=True): channel_node = autoplay_node.get(item.channel, {}) settings_node = channel_node.get('settings', {}) AP = get_setting('autoplay') or settings_node['active'] - APS = get_setting('autoplay_server_list') + HS = config.get_setting('hide_servers') if CL and not AP: if get_setting('checklinks', item.channel): @@ -994,7 +1056,7 @@ def controls(itemlist, item, AutoPlay=True, CheckLinks=True, down_load=True): checklinks_number = get_setting('checklinks_number') itemlist = servertools.check_list_links(itemlist, checklinks_number) - if AutoPlay == True and not 'downloads' in inspect.stack()[3][1] + inspect.stack()[4][1] and item.contentChannel != 'videolibrary': + if AutoPlay == True and not 'downloads' in inspect.stack()[3][1] + inspect.stack()[4][1]: autoplay.start(itemlist, item) if item.contentChannel != 'videolibrary': videolibrary(itemlist, item, function_level=3) @@ -1010,7 +1072,7 @@ def controls(itemlist, item, AutoPlay=True, CheckLinks=True, down_load=True): VL = True except: pass - if not AP or VL or not APS: + if not AP or VL or not HS: return itemlist def filterLang(item, itemlist): diff --git a/core/tmdb.py b/core/tmdb.py index 65c6ff0b..a369f6d0 100644 --- a/core/tmdb.py +++ b/core/tmdb.py @@ -160,7 +160,7 @@ def cache_response(fn): conn = sqlite3.connect(fname, timeout=15) c = conn.cursor() url = re.sub('&year=-', '', args[0]) - logger.error('la url %s' % url) + # logger.error('la url %s' % url) url_base64 = base64.b64encode(url) c.execute("SELECT response, added FROM tmdb_cache WHERE url=?", (url_base64,)) row = c.fetchone() @@ -185,7 +185,7 @@ def cache_response(fn): # error al obtener los datos except Exception, ex: message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) - logger.error("error en: %s" % message) + logger.error("error in: %s" % message) return result @@ -214,10 +214,10 @@ def set_infoLabels(source, seekTmdb=True, idioma_busqueda=def_lang, forced=False start_time = time.time() if type(source) == list: ret = set_infoLabels_itemlist(source, seekTmdb, idioma_busqueda) - logger.debug("Se han obtenido los datos de %i enlaces en %f segundos" % (len(source), time.time() - start_time)) + logger.debug("The data of %i links were obtained in %f seconds" % (len(source), time.time() - start_time)) else: ret = set_infoLabels_item(source, seekTmdb, idioma_busqueda) - logger.debug("Se han obtenido los datos del enlace en %f segundos" % (time.time() - start_time)) + logger.debug("The data of %i links were obtained in %f seconds" % (time.time() - start_time)) return ret @@ -242,6 +242,7 @@ def set_infoLabels_itemlist(item_list, seekTmdb=False, idioma_busqueda=def_lang, negativo en caso contrario. @rtype: list """ + if not config.get_setting('tmdb_active') and not forced: return import threading @@ -314,7 +315,7 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None try: numtemporada = int(item.infoLabels['season']) except ValueError: - logger.debug("El numero de temporada no es valido") + logger.debug("The season number is not valid.") return -1 * len(item.infoLabels) if lock: @@ -340,7 +341,7 @@ def set_infoLabels_item(item, seekTmdb=True, idioma_busqueda=def_lang, lock=None try: episode = int(item.infoLabels['episode']) except ValueError: - logger.debug("El número de episodio (%s) no es valido" % repr(item.infoLabels['episode'])) + logger.debug("The episode number (%s) is not valid" % repr(item.infoLabels['episode'])) return -1 * len(item.infoLabels) # Tenemos numero de temporada y numero de episodio validos... @@ -847,7 +848,7 @@ class Tmdb(object): self.__discover() else: - logger.debug("Creado objeto vacio") + logger.debug("Created empty object") @staticmethod @cache_response @@ -879,7 +880,7 @@ class Tmdb(object): # error al obtener los datos except Exception, ex: message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) - logger.error("error en: %s" % message) + logger.error("error in: %s" % message) dict_data = {} return dict_data @@ -895,7 +896,7 @@ class Tmdb(object): url = ('http://api.themoviedb.org/3/genre/%s/list?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s' % (tipo, idioma)) try: - logger.info("[Tmdb.py] Rellenando dicionario de generos") + logger.info("[Tmdb.py] Filling in dictionary of genres") resultado = cls.get_json(url) lista_generos = resultado["genres"] @@ -903,7 +904,7 @@ class Tmdb(object): for i in lista_generos: cls.dic_generos[idioma][tipo][str(i["id"])] = i["name"] except: - logger.error("Error generando diccionarios") + logger.error("Error generating dictionaries") def __by_id(self, source='tmdb'): @@ -923,7 +924,7 @@ class Tmdb(object): '&language=%s' % (self.busqueda_id, source, self.busqueda_idioma)) buscando = "%s: %s" % (source.capitalize(), self.busqueda_id) - logger.info("[Tmdb.py] Buscando %s:\n%s" % (buscando, url)) + logger.info("[Tmdb.py] Searching %s:\n%s" % (buscando, url)) resultado = self.get_json(url) if resultado: @@ -940,8 +941,8 @@ class Tmdb(object): else: # No hay resultados de la busqueda - msg = "La busqueda de %s no dio resultados." % buscando - logger.debug(msg) + msg = "The search of %s gave no results" % buscando + # logger.debug(msg) def __search(self, index_results=0, page=1): self.result = ResultDictDefault() @@ -963,7 +964,7 @@ class Tmdb(object): url += '&year=%s' % self.busqueda_year buscando = self.busqueda_texto.capitalize() - logger.info("[Tmdb.py] Buscando %s en pagina %s:\n%s" % (buscando, page, url)) + logger.info("[Tmdb.py] Searching %s on page %s:\n%s" % (buscando, page, url)) resultado = self.get_json(url) total_results = resultado.get("total_results", 0) @@ -984,7 +985,7 @@ class Tmdb(object): if index_results >= len(results): # Se ha solicitado un numero de resultado mayor de los obtenidos logger.error( - "La busqueda de '%s' dio %s resultados para la pagina %s\nImposible mostrar el resultado numero %s" + "The search for '%s' gave %s results for the page %s \n It is impossible to show the result number %s" % (buscando, len(results), page, index_results)) return 0 @@ -997,7 +998,7 @@ class Tmdb(object): else: # No hay resultados de la busqueda - msg = "La busqueda de '%s' no dio resultados para la pagina %s" % (buscando, page) + msg = "The search for '%s' gave no results for page %s" % (buscando, page) logger.error(msg) return 0 @@ -1021,7 +1022,7 @@ class Tmdb(object): url = ('http://api.themoviedb.org/3/%s?api_key=a1ab8b8669da03637a4b98fa39c39228&%s' % (type_search, "&".join(params))) - logger.info("[Tmdb.py] Buscando %s:\n%s" % (type_search, url)) + logger.info("[Tmdb.py] Searcing %s:\n%s" % (type_search, url)) resultado = self.get_json(url) total_results = resultado.get("total_results", -1) @@ -1045,7 +1046,7 @@ class Tmdb(object): if index_results >= len(results): logger.error( - "La busqueda de '%s' no dio %s resultados" % (type_search, index_results)) + "The search for '%s' did not give %s results" % (type_search, index_results)) return 0 # Retornamos el numero de resultados de esta pagina @@ -1061,7 +1062,7 @@ class Tmdb(object): return len(self.results) else: # No hay resultados de la busqueda - logger.error("La busqueda de '%s' no dio resultados" % type_search) + logger.error("The search for '%s' gave no results" % type_search) return 0 def load_resultado(self, index_results=0, page=1): @@ -1311,20 +1312,20 @@ class Tmdb(object): url = "http://api.themoviedb.org/3/tv/%s/season/%s?api_key=a1ab8b8669da03637a4b98fa39c39228&language=%s" \ "&append_to_response=credits" % (self.result["id"], numtemporada, self.busqueda_idioma) - buscando = "id_Tmdb: " + str(self.result["id"]) + " temporada: " + str(numtemporada) + "\nURL: " + url - logger.info("[Tmdb.py] Buscando " + buscando) + buscando = "id_Tmdb: " + str(self.result["id"]) + " season: " + str(numtemporada) + "\nURL: " + url + logger.info("[Tmdb.py] Searcing " + buscando) try: self.temporada[numtemporada] = self.get_json(url) except: - logger.error("No se ha podido obtener la temporada") + logger.error("Unable to get the season") self.temporada[numtemporada] = {"status_code": 15, "status_message": "Failed"} self.temporada[numtemporada] = {"episodes": {}} if "status_code" in self.temporada[numtemporada]: #Se ha producido un error msg = config.get_localized_string(70496) + buscando + config.get_localized_string(70497) - msg += "\nError de tmdb: %s %s" % ( + msg += "\nTmdb error: %s %s" % ( self.temporada[numtemporada]["status_code"], self.temporada[numtemporada]["status_message"]) logger.debug(msg) self.temporada[numtemporada] = {} @@ -1351,7 +1352,7 @@ class Tmdb(object): capitulo = int(capitulo) numtemporada = int(numtemporada) except ValueError: - logger.debug("El número de episodio o temporada no es valido") + logger.debug("The episode or season number is not valid") return {} temporada = self.get_temporada(numtemporada) @@ -1361,7 +1362,7 @@ class Tmdb(object): if len(temporada["episodes"]) == 0 or len(temporada["episodes"]) < capitulo: # Se ha producido un error - logger.error("Episodio %d de la temporada %d no encontrado." % (capitulo, numtemporada)) + logger.error("Episode %d of the season %d not found." % (capitulo, numtemporada)) return {} ret_dic = dict() diff --git a/lib/cloudscraper/__init__.py b/lib/cloudscraper/__init__.py index 541b32cf..f4e584e5 100644 --- a/lib/cloudscraper/__init__.py +++ b/lib/cloudscraper/__init__.py @@ -1,4 +1,3 @@ -# https://github.com/VeNoMouS/cloudscraper import logging import re import sys @@ -38,7 +37,7 @@ except ImportError: # ------------------------------------------------------------------------------- # -__version__ = '1.2.16' +__version__ = '1.2.19' # ------------------------------------------------------------------------------- # @@ -163,7 +162,6 @@ class CloudScraper(Session): def request(self, method, url, *args, **kwargs): # pylint: disable=E0203 - if kwargs.get('proxies') and kwargs.get('proxies') != self.proxies: self.proxies = kwargs.get('proxies') @@ -198,6 +196,7 @@ class CloudScraper(Session): else: if not resp.is_redirect and resp.status_code not in [429, 503]: self._solveDepthCnt = 0 + return resp # ------------------------------------------------------------------------------- # @@ -243,7 +242,7 @@ class CloudScraper(Session): return False # ------------------------------------------------------------------------------- # - # check if the response contains a valid Cloudflare reCaptcha challenge + # check if the response contains Firewall 1020 Error # ------------------------------------------------------------------------------- # @staticmethod @@ -270,10 +269,7 @@ class CloudScraper(Session): def is_Challenge_Request(self, resp): if self.is_Firewall_Blocked(resp): sys.tracebacklimit = 0 - raise RuntimeError( - 'Cloudflare has a restriction on your IP (Code 1020 Detected), ' - 'you are BLOCKED.' - ) + raise RuntimeError('Cloudflare has blocked this request (Code 1020 Detected).') if self.is_reCaptcha_Challenge(resp) or self.is_IUAM_Challenge(resp): return True @@ -434,6 +430,7 @@ class CloudScraper(Session): # ------------------------------------------------------------------------------- # if submit_url: + def updateAttr(obj, name, newValue): try: obj[name].update(newValue) @@ -450,13 +447,18 @@ class CloudScraper(Session): 'data', submit_url['data'] ) + + urlParsed = urlparse(resp.url) cloudflare_kwargs['headers'] = updateAttr( cloudflare_kwargs, 'headers', - {'Referer': resp.url} + { + 'Origin': '{}://{}'.format(urlParsed.scheme, urlParsed.netloc), + 'Referer': resp.url + } ) - ret = self.request( + challengeSubmitResponse = self.request( 'POST', submit_url['url'], **cloudflare_kwargs @@ -464,13 +466,44 @@ class CloudScraper(Session): # ------------------------------------------------------------------------------- # # 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 ret.is_redirect: - return ret + if not challengeSubmitResponse.is_redirect: + return challengeSubmitResponse + else: + cloudflare_kwargs = deepcopy(kwargs) + + if not urlparse(challengeSubmitResponse.headers['Location']).netloc: + cloudflare_kwargs['headers'] = updateAttr( + cloudflare_kwargs, + 'headers', + {'Referer': '{}://{}'.format(urlParsed.scheme, urlParsed.netloc)} + ) + return self.request( + resp.request.method, + '{}://{}{}'.format( + urlParsed.scheme, + urlParsed.netloc, + challengeSubmitResponse.headers['Location'] + ), + **cloudflare_kwargs + ) + else: + redirectParsed = urlparse(challengeSubmitResponse.headers['Location']) + cloudflare_kwargs['headers'] = updateAttr( + cloudflare_kwargs, + 'headers', + {'Referer': '{}://{}'.format(redirectParsed.scheme, redirectParsed.netloc)} + ) + return self.request( + resp.request.method, + challengeSubmitResponse.headers['Location'], + **cloudflare_kwargs + ) # ------------------------------------------------------------------------------- # - # Cloudflare is doing http 3xx instead of pass through again.... + # We shouldn't be here... # Re-request the original query and/or process again.... # ------------------------------------------------------------------------------- # @@ -554,6 +587,17 @@ 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 " + "version (>= OpenSSL 1.1.1) in order to support TLS 1.3 required by Cloudflare, " + "You may encounter an unexpected reCaptcha or cloudflare 1020 blocks.".format( + ssl.OPENSSL_VERSION + ) + ) + +# ------------------------------------------------------------------------------- # + create_scraper = CloudScraper.create_scraper get_tokens = CloudScraper.get_tokens get_cookie_string = CloudScraper.get_cookie_string diff --git a/lib/unshortenit.py b/lib/unshortenit.py index f87e4297..224e5adf 100644 --- a/lib/unshortenit.py +++ b/lib/unshortenit.py @@ -37,7 +37,7 @@ class UnshortenIt(object): _rapidcrypt_regex = r'rapidcrypt\.net' _cryptmango_regex = r'cryptmango|xshield\.net' _vcrypt_regex = r'vcrypt\.net' - _linkup_regex = r'linkup\.pro' + _linkup_regex = r'linkup\.pro|buckler.link' _maxretries = 5 diff --git a/platformcode/launcher.py b/platformcode/launcher.py index 31f604c0..c89d7a7d 100644 --- a/platformcode/launcher.py +++ b/platformcode/launcher.py @@ -429,6 +429,7 @@ def limit_itemlist(itemlist): def play_from_library(item): + itemlist=[] """ Los .strm al reproducirlos desde kodi, este espera que sea un archivo "reproducible" asi que no puede contener más items, como mucho se puede colocar un dialogo de seleccion. @@ -440,12 +441,13 @@ def play_from_library(item): @param item: elemento con información """ logger.info() - #logger.debug("item: \n" + item.tostring('\n')) + # logger.debug("item: \n" + item.tostring('\n')) import xbmcgui import xbmcplugin import xbmc - from time import sleep + from time import sleep, time + from specials import nextep # Intentamos reproducir una imagen (esto no hace nada y ademas no da error) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, @@ -458,8 +460,10 @@ def play_from_library(item): # modificamos el action (actualmente la videoteca necesita "findvideos" ya que es donde se buscan las fuentes item.action = "findvideos" + check_next_ep = nextep.check(item) - window_type = config.get_setting("window_type", "videolibrary") + + window_type = 1 if check_next_ep else config.get_setting("window_type", "videolibrary") # y volvemos a lanzar kodi if xbmc.getCondVisibility('Window.IsMedia') and not window_type == 1: @@ -467,66 +471,85 @@ def play_from_library(item): xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")") else: + # Ventana emergente - from specials import videolibrary + item.show_server = True + + from specials import videolibrary, autoplay p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), config.get_localized_string(70004)) p_dialog.update(0, '') itemlist = videolibrary.findvideos(item) + if check_next_ep and autoplay.is_active(item.contentChannel): + p_dialog.update(100, '') + sleep(0.5) + p_dialog.close() + item = nextep.return_item(item) + if item.next_ep: + return play_from_library(item) - while platformtools.is_playing(): - # Ventana convencional - sleep(5) - p_dialog.update(50, '') + else: + while platformtools.is_playing(): + # Ventana convencional + sleep(5) + p_dialog.update(50, '') - '''# Se filtran los enlaces segun la lista negra - if config.get_setting('filter_servers', "servers"): - itemlist = servertools.filter_servers(itemlist)''' + it = item + if item.show_server or not check_next_ep: - # Se limita la cantidad de enlaces a mostrar - if config.get_setting("max_links", "videolibrary") != 0: - itemlist = limit_itemlist(itemlist) + '''# Se filtran los enlaces segun la lista negra + if config.get_setting('filter_servers', "servers"): + itemlist = servertools.filter_servers(itemlist)''' - # Se "limpia" ligeramente la lista de enlaces - if config.get_setting("replace_VD", "videolibrary") == 1: - itemlist = reorder_itemlist(itemlist) + # Se limita la cantidad de enlaces a mostrar + if config.get_setting("max_links", "videolibrary") != 0: + itemlist = limit_itemlist(itemlist) + + # Se "limpia" ligeramente la lista de enlaces + if config.get_setting("replace_VD", "videolibrary") == 1: + itemlist = reorder_itemlist(itemlist) - import time - p_dialog.update(100, '') - time.sleep(0.5) - p_dialog.close() + p_dialog.update(100, '') + sleep(0.5) + p_dialog.close() - if len(itemlist) > 0: - while not xbmc.Monitor().abortRequested(): - # El usuario elige el mirror - opciones = [] - for item in itemlist: - opciones.append(item.title) + if len(itemlist) > 0: + while not xbmc.Monitor().abortRequested(): + # El usuario elige el mirror + opciones = [] + for item in itemlist: + opciones.append(item.title) - # Se abre la ventana de seleccion - if (item.contentSerieName != "" and - item.contentSeason != "" and - item.contentEpisodeNumber != ""): - cabecera = ("%s - %sx%s -- %s" % - (item.contentSerieName, - item.contentSeason, - item.contentEpisodeNumber, - config.get_localized_string(30163))) - else: - cabecera = config.get_localized_string(30163) + # Se abre la ventana de seleccion + if (item.contentSerieName != "" and + item.contentSeason != "" and + item.contentEpisodeNumber != ""): + cabecera = ("%s - %sx%s -- %s" % + (item.contentSerieName, + item.contentSeason, + item.contentEpisodeNumber, + config.get_localized_string(30163))) + else: + cabecera = config.get_localized_string(30163) - seleccion = platformtools.dialog_select(cabecera, opciones) - if seleccion == -1: - return - else: - item = videolibrary.play(itemlist[seleccion])[0] - item.play_from = 'window' - platformtools.play_video(item) + seleccion = platformtools.dialog_select(cabecera, opciones) + + if seleccion == -1: + return + else: + item = videolibrary.play(itemlist[seleccion])[0] + item.play_from = 'window' + platformtools.play_video(item) + + if (platformtools.is_playing() and item.action) or item.server == 'torrent' or autoplay.is_active(item.contentChannel): + break + + if it.show_server and check_next_ep: + nextep.run(it) + sleep(0.5) + p_dialog.close() - from specials import autoplay - if (platformtools.is_playing() and item.action) or item.server == 'torrent' or autoplay.is_active(item.contentChannel): - break diff --git a/platformcode/platformtools.py b/platformcode/platformtools.py index dd81d461..937573f0 100644 --- a/platformcode/platformtools.py +++ b/platformcode/platformtools.py @@ -12,7 +12,7 @@ import os import sys import urllib -import config +# import config import xbmc import xbmcaddon import xbmcgui @@ -22,7 +22,7 @@ from channelselector import get_thumb from core import channeltools from core import trakt_tools, scrapertools from core.item import Item -from platformcode import logger, keymaptools +from platformcode import logger, keymaptools, config from platformcode import unify addon = xbmcaddon.Addon('plugin.video.kod') @@ -683,8 +683,8 @@ def is_playing(): def play_video(item, strm=False, force_direct=False, autoplay=False): logger.info() - if item.play_from == 'window': - force_direct=True + # if item.play_from == 'window': + # force_direct=True # logger.debug(item.tostring('\n')) logger.debug('item play: %s'%item) xbmc_player = XBMCPlayer() @@ -726,8 +726,8 @@ def play_video(item, strm=False, force_direct=False, autoplay=False): mediaurl, view, mpd = get_video_seleccionado(item, seleccion, video_urls) if mediaurl == "": return - # no certificate verification - mediaurl = mediaurl.replace('https://', 'http://') + # # no certificate verification + # mediaurl = mediaurl.replace('https://', 'http://') # se obtiene la información del video. if not item.contentThumbnail: @@ -1079,7 +1079,7 @@ def get_video_seleccionado(item, seleccion, video_urls): def set_player(item, xlistitem, mediaurl, view, strm): logger.info() - logger.debug("item:\n" + item.tostring('\n')) + # logger.debug("item:\n" + item.tostring('\n')) # Movido del conector "torrent" aqui if item.server == "torrent": @@ -1101,7 +1101,7 @@ def set_player(item, xlistitem, mediaurl, view, strm): download_and_play.download_and_play(mediaurl, "download_and_play.mp4", config.get_setting("downloadpath")) return - elif config.get_setting("player_mode") == 0 or \ + elif config.get_setting("player_mode") == 0 or item.play_from == 'window' or\ (config.get_setting("player_mode") == 3 and mediaurl.startswith("rtmp")): # Añadimos el listitem a una lista de reproducción (playlist) playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) diff --git a/platformcode/subtitletools.py b/platformcode/subtitletools.py index fa55ddb7..bd30ff93 100644 --- a/platformcode/subtitletools.py +++ b/platformcode/subtitletools.py @@ -251,7 +251,7 @@ def searchSubtitle(item): def saveSubtitleName(item): - if item.show in item.title: + if not item.show or item.show in item.title: title = item.title else: title = item.show + " - " + item.title diff --git a/platformcode/updater.py b/platformcode/updater.py index be72a0d2..26570c2e 100644 --- a/platformcode/updater.py +++ b/platformcode/updater.py @@ -132,8 +132,12 @@ def check(): urllib.urlretrieve(file['raw_url'], os.path.join(addonDir, file['filename'])) else: # è un file NON testuale, lo devo scaricare # se non è già applicato - if not (filetools.isfile(addonDir + file['filename']) and getSha(addonDir + file['filename']) == file['sha']): - urllib.urlretrieve(file['raw_url'], os.path.join(addonDir, file['filename'])) + filename = os.path.join(addonDir, file['filename']) + dirname = os.path.dirname(filename) + if not (filetools.isfile(addonDir + file['filename']) and getSha(filename) == file['sha']): + if not os.path.exists(dirname): + os.makedirs(dirname) + urllib.urlretrieve(file['raw_url'], filename) alreadyApplied = False elif file['status'] == 'removed': remove(addonDir+file["filename"]) diff --git a/resources/language/English/strings.po b/resources/language/English/strings.po index c2ccc5db..a31357a3 100644 --- a/resources/language/English/strings.po +++ b/resources/language/English/strings.po @@ -5702,7 +5702,39 @@ msgid "Enter another year..." msgstr "" msgctxt "#70746" -msgid "Hide server selection from Autoplay" +msgid "When playback ends" +msgstr "" + +msgctxt "#70747" +msgid "Hide server selection" +msgstr "" + +msgctxt "#70748" +msgid "Go to the next episode" +msgstr "" + +msgctxt "#70749" +msgid "Seconds before notification" +msgstr "" + +msgctxt "#70750" +msgid "Next Episode in" +msgstr "" + +msgctxt "#70751" +msgid "seconds" +msgstr "" + +msgctxt "#70752" +msgid "Do nothing" +msgstr "" + +msgctxt "#70753" +msgid "Playback" +msgstr "" + +msgctxt "#70754" +msgid "Compact mode" msgstr "" # DNS start [ settings and declaration ] diff --git a/resources/language/Italian/strings.po b/resources/language/Italian/strings.po index adc60e7a..7e9473f7 100644 --- a/resources/language/Italian/strings.po +++ b/resources/language/Italian/strings.po @@ -5706,8 +5706,40 @@ msgid "Enter another year..." msgstr "Inserisci un altro anno..." msgctxt "#70746" -msgid "Hide server selection from Autoplay" -msgstr "Nascondi la selezione del server da Autoplay" +msgid "When playback ends" +msgstr "Al termine della riproduzione" + +msgctxt "#70747" +msgid "Hide server selection" +msgstr "Nascondi la selezione del server" + +msgctxt "#70748" +msgid "Go to the next episode" +msgstr "Vai all'episodio successivo" + +msgctxt "#70749" +msgid "Seconds before notification" +msgstr "Secondi prima della notifica" + +msgctxt "#70750" +msgid "Next Episode in" +msgstr "Episodio Successivo fra" + +msgctxt "#70751" +msgid "seconds" +msgstr "secondi" + +msgctxt "#70752" +msgid "Do nothing" +msgstr "Non fare nulla" + +msgctxt "#70753" +msgid "Playback" +msgstr "Riproduzione" + +msgctxt "#70754" +msgid "Compact mode" +msgstr "Modalità compatta" # DNS start [ settings and declaration ] msgctxt "#707401" diff --git a/resources/settings.xml b/resources/settings.xml index 8bca2334..b40e95e4 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -2,17 +2,12 @@ <settings> <!-- General --> <category label="70168"> - <setting id="player_mode" type="enum" values="Direct|SetResolvedUrl|Built-In|Download and Play" label="30044" default="1"/> - <setting id="default_action" type="enum" lvalues="30006|30007|30008" label="30005" default="0"/> - <setting id="autoplay" type="bool" label="70562" default="false" visible="true"/> -<!-- <setting id="autoplay_next" type="bool" label="Riproduci episodio successivo" default="false" visible="true"/>--> - <setting id="autoplay_server_list" type="bool" label="70746" default="false" visible="true"/> - <setting id="checklinks" type="bool" label="30020" default="false"/> - <setting id="checklinks_number" type="enum" values="5|10|15|20" label="30021" default="0" visible="eq(-1,true)"/> + <setting id="thumbnail_type" type="enum" lvalues="30011|30012|30200" label="30010" default="2"/> <setting id="channel_language" type="labelenum" values="auto|all|ita" label="30019" default="all"/> <setting id="trakt_sync" type="bool" label="70109" default="false"/> <setting id="forceview" type="bool" label="30043" default="false"/> + <setting id="faster_item_serialization" type="bool" label="30300" default="false"/> <setting id="resolver_dns" type="bool" label="707408" default="true" enable="true" visible="true"/> <setting id="debug" type="bool" label="30003" default="false"/> <setting label="70169" type="lsep"/> @@ -40,6 +35,16 @@ <setting id="enable_library_menu" label="30131" type="bool" default="true"/> </category> + <!-- Playback --> + <category label="70753"> + <setting id="player_mode" type="enum" values="Direct|SetResolvedUrl|Built-In|Download and Play" label="30044" default="1"/> + <setting id="default_action" type="enum" lvalues="30006|30007|30008" label="30005" default="0"/> + <setting id="autoplay" type="bool" label="70562" default="false" visible="true"/> + <setting id="hide_servers" type="bool" label="70747" default="false" visible="eq(-1,true)"/> + <setting id="checklinks" type="bool" label="30020" default="false"/> + <setting id="checklinks_number" type="enum" values="5|10|15|20" label="30021" default="0" visible="eq(-1,true)"/> + </category> + <!-- Videolibrary --> <category label="30131"> <!-- <setting id="downloadpath" type="folder" label="30017" default=""/> @@ -53,6 +58,9 @@ <setting id="videolibrary_kodi_force" type="bool" label="" default="false" visible="false"/> <setting id="videolibrary_kodi" type="bool" label="70120" enable="lt(-1,2)+eq(0,false)" default="false"/> <setting id="videolibrary_max_quality" type="bool" label="70729" default="false" visible="true"/> + <setting id="next_ep" type="enum" label="70746" lvalues="70752|70747|70748" default="0"/> + <setting id="next_ep_type" type="bool" label="70754" default="false" visible="eq(-1,2)"/> + <setting id="next_ep_seconds" type="enum" values="20|30|40|50|60" label="70749" default="2" visible="!eq(-2,0)"/> </category> @@ -70,61 +78,8 @@ <setting id="custom_theme" type="folder" label="70565" default="" visible="eq(-1,true)"/> <setting id="infoplus_set" type="labelenum" label="70128" lvalues="70129|70130" default="70129"/> <setting id="video_thumbnail_type" type="enum" label="70131" lvalues="70132|70133" default="0"/> - <!-- <setting label="70167" type="lsep"/> - <setting id="unify" type="bool" label="70134" default="false"/> - <setting id="title_color" type="bool" label="70135" default="false" visible="eq(-1,true)"/> - <setting id="movie_color" type="labelenum" label="70137" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-1,true)+eq(-2,true)"/> - <setting id="tvshow_color" type="labelenum" label="30123" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-2,true)+eq(-3,true)"/> - <setting id="year_color" type="labelenum" label="60232" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-3,true)+eq(-4,true)"/> - <setting id="rating_1_color" type="labelenum" label="70138" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-4,true)+eq(-5,true)"/> - <setting id="rating_2_color" type="labelenum" label="70139" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-5,true)+eq(-6,true)"/> - <setting id="rating_3_color" type="labelenum" label="70140" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-6,true)+eq(-7,true)"/> - <setting id="quality_color" type="labelenum" label="70141" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-7,true)+eq(-8,true)"/> - <setting id="cast_color" type="labelenum" label="59980" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-8,true)+eq(-9,true)"/> - <setting id="lat_color" type="labelenum" label="59981" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-9,true)+eq(-10,true)"/> - <setting id="vose_color" type="labelenum" label="70142" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-10,true)+eq(-11,true)"/> - <setting id="sub-ita_color" type="labelenum" label="70566" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-11,true)+eq(-12,true)"/> - <setting id="vos_color" type="labelenum" label="70143" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-12,true)+eq(-13,true)"/> - <setting id="vo_color" type="labelenum" label="70144" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-13,true)+eq(-14,true)"/> - <setting id="server_color" type="labelenum" label="70145" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-14,true)+eq(-15,true)"/> - <setting id="library_color" type="labelenum" label="70146" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-15,true)+eq(-16,true)"/> - <setting id="update_color" type="labelenum" label="70147" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-16,true)+eq(-17,true)"/> - <setting id="no_update_color" type="labelenum" label="70148" - values="[COLOR white]white[/COLOR]|[COLOR cyan]cyan[/COLOR]|[COLOR deepskyblue]deepskyblue[/COLOR]|[COLOR firebrick]firebrick[/COLOR]|[COLOR gold]gold[/COLOR]|[COLOR goldenrod]goldenrod[/COLOR]|[COLOR hotpink]hotpink[/COLOR]|[COLOR limegreen]limegreen[/COLOR]|[COLOR orange]orange[/COLOR]|[COLOR orchid]orchid[/COLOR]|[COLOR red]red[/COLOR]|[COLOR salmon]salmon[/COLOR]|[COLOR yellow]yellow[/COLOR]" - default="white" visible="eq(-17,true)+eq(-18,true)"/> --> </category> + <!-- Other --> <category label="70149"> <setting label="70150" type="lsep"/> @@ -137,6 +92,7 @@ <setting type="sep"/> <setting label="70154" type="lsep"/> + <setting id="tmdb_active" default="true" visible="false"/> <setting id="tmdb_threads" type="labelenum" values="5|10|15|20|25|30" label="70155" default="20"/> <setting id="tmdb_plus_info" type="bool" label="70156" default="false"/> <setting id="tmdb_cache" type="bool" label="70157" default="true"/> @@ -155,9 +111,6 @@ <setting label="Lista activa" type="text" id="lista_activa" default="kodfavorites-default.json" visible="false"/> - <!-- <setting type="sep"/> - <setting label="70583" type="lsep"/> - <setting id="addon_quasar_update" type="bool" label="70584" default="false"/> --> </category> <!-- Custom Start --> <category label="70121"> @@ -172,5 +125,4 @@ </category> - </settings> diff --git a/resources/skins/Default/720p/NextDialog.xml b/resources/skins/Default/720p/NextDialog.xml new file mode 100644 index 00000000..2455d71d --- /dev/null +++ b/resources/skins/Default/720p/NextDialog.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window> + <defaultcontrol always="true">20</defaultcontrol> + <onload>Dialog.Close(fullscreeninfo,true)</onload> + <onload>Dialog.Close(videoosd,true)</onload> + <controls> + <control type="group"> + <animation type="WindowOpen" reversible="false"> + <effect type="fade" start="0" end="100" time="600" /> + <effect type="slide" start="115,0" end="0,0" time="600" /> + </animation> + <animation type="WindowClose" reversible="false"> + <effect type="fade" start="100" end="0" time="400" /> + <effect type="slide" start="0,0" end="115,0" time="400" /> + </animation> + <control type="group"> + <right>0</right> + <top>15</top> + <height>40</height> + <width>100%</width> + <!-- Background --> + <control type="image"> + <top>0</top> + <width>100%</width> + <height>40</height> + <texture colordiffuse="00111111">NextDialog/background-diffuse.png</texture> + </control> + <control type="group"> + <top>0</top> + <right>0</right> + <width>100%</width> + <!-- buttons --> + <control type="button" id="3012"> + <left>-1000</left> + <top>-1000</top> + <height>1</height> + <width>1</width> + </control> + <control type="grouplist" id="20"> + <orientation>horizontal</orientation> + <height>40</height> + <itemgap>0</itemgap> + <align>right</align> + <control type="button" id="11"> + <label>$ADDON[plugin.video.kod 70750] $INFO[Player.TimeRemaining(ss),,] $ADDON[plugin.video.kod 70751]</label> + <onclick>SendClick(3012)</onclick> + <height>40</height> + <width min="50">auto</width> + <font>font30_title</font> + <textoffsetx>20</textoffsetx> + <textcolor>80FFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <selectedcolor>80FFFFFF</selectedcolor> + <shadowcolor>22000000</shadowcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus border="10" colordiffuse="88232323">NextDialog/background-diffuse.png</texturefocus> + <texturenofocus border="10" colordiffuse="88232323">NextDialog/background-diffuse.png</texturenofocus> + <pulseonselect>no</pulseonselect> + </control> + <control type="button" id="3013"> + <label>$ADDON[plugin.video.kod 60396]</label> + <height>40</height> + <width min="50">auto</width> + <font>font30_title</font> + <textoffsetx>20</textoffsetx> + <textcolor>80FFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <selectedcolor>80FFFFFF</selectedcolor> + <shadowcolor>22000000</shadowcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus border="10" colordiffuse="88232323">NextDialog/background-diffuse.png</texturefocus> + <texturenofocus border="10" colordiffuse="88232323">NextDialog/background-diffuse.png</texturenofocus> + <pulseonselect>no</pulseonselect> + </control> + <control type="image"> + <top>0</top> + <width>30</width> + <height>40</height> + <texture colordiffuse="88232323">NextDialog/background-diffuse.png</texture> + </control> + </control> + </control> + </control> + </control> + </controls> +</window> \ No newline at end of file diff --git a/resources/skins/Default/720p/NextDialogCompact.xml b/resources/skins/Default/720p/NextDialogCompact.xml new file mode 100644 index 00000000..2164ace3 --- /dev/null +++ b/resources/skins/Default/720p/NextDialogCompact.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window> + <defaultcontrol always="true">20</defaultcontrol> + <onload>Dialog.Close(fullscreeninfo,true)</onload> + <onload>Dialog.Close(videoosd,true)</onload> + <controls> + <control type="group"> + <animation type="WindowOpen" reversible="false"> + <effect type="fade" start="0" end="100" time="600" /> + <effect type="slide" start="115,0" end="0,0" time="600" /> + </animation> + <animation type="WindowClose" reversible="false"> + <effect type="fade" start="100" end="0" time="400" /> + <effect type="slide" start="0,0" end="115,0" time="400" /> + </animation> + <control type="group"> + <right>0</right> + <top>15</top> + <height>50</height> + <width>100%</width> + <!-- Background --> + <control type="image"> + <top>0</top> + <width>100%</width> + <height>40</height> + <!-- <texture colordiffuse="00111111">NextDialog/button-bg.png</texture> --> + </control> + <control type="group"> + <top>0</top> + <right>0</right> + <width>100%</width> + <!-- buttons --> + <control type="button" id="3012"> + <left>-1000</left> + <top>-1000</top> + <height>1</height> + <width>1</width> + </control> + <control type="grouplist" id="20"> + <orientation>horizontal</orientation> + <height>40</height> + <itemgap>0</itemgap> + <align>right</align> + <control type="button" id="11"> + <label>[B]$INFO[Player.TimeRemaining(ss),,][/B]</label> + <onclick>SendClick(3012)</onclick> + <!-- <visible>!Integer.IsGreater(Player.TimeRemaining,59)</visible> --> + <height>40</height> + <width>65</width> + <font>font30_title</font> + <textoffsetx>12</textoffsetx> + <textcolor>80FFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <selectedcolor>80FFFFFF</selectedcolor> + <shadowcolor>22000000</shadowcolor> + <aligny>center</aligny> + <align>left</align> + <texturefocus border="10">NextDialog/play-fo.png</texturefocus> + <texturenofocus border="10">NextDialog/play-nf.png</texturenofocus> + <pulseonselect>no</pulseonselect> + </control> + <control type="button" id="3013"> + <label></label> + <height>40</height> + <width>40</width> + <font>font30_title</font> + <textoffsetx>30</textoffsetx> + <textcolor>80FFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <selectedcolor>80FFFFFF</selectedcolor> + <shadowcolor>22000000</shadowcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus border="10">NextDialog/close-fo.png</texturefocus> + <texturenofocus border="10">NextDialog/close-nf.png</texturenofocus> + <pulseonselect>no</pulseonselect> + </control> + <control type="image"> + <top>0</top> + <width>20</width> + <height>40</height> + <texture>NextDialog/background.png</texture> + </control> + </control> + </control> + </control> + </control> + </controls> +</window> \ No newline at end of file diff --git a/resources/skins/Default/media/NextDialog/background-diffuse.png b/resources/skins/Default/media/NextDialog/background-diffuse.png new file mode 100644 index 0000000000000000000000000000000000000000..f636860396124eee9d8e0ad0a22357ac4938892c GIT binary patch literal 1627 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51SA=YQ-3iqF#pR8i71Ki^|4CM&(%vz$xlkv ztH><?$}=$7*jE%JCTFLXC?ut(XXe=|z2CiGNg*@ERw>-n*TA>HIW;5GqpB!1xXLdi zxhgx^GDXSWj?1RP3TQxXYDuC(MQ%=Bu~mhw64+cTAR8pCucQE0Qj%?}6yY17;GAES zs$i;Ts%M~N$E9FXl#*r@<l+X^4CL7=Wt5Z@Sn2DRmzV368|&p4rRy77T3YHG80i}s z=>k>g7FXt#Bv$C=6)S^`fSBQuTAW;zSx}OhpQivaGchT@w8U0P2`H}sHM}G<4Pkm- zG1U9OfY1lY=o{)8=p!o!dJFCr6x9`p1=tk(`r=ofn~P$6aY;}r4(GruC=RJCNYxKY zEzU13N=^kTS4MVUYDH$6YXscR<jjIZy{rO#kT8bx5=(*60u5Ibzra-kRY05v3JWXG z+{E-$pVYkck_=lV3j?sV2#N5_6eMwAI9WLtr6!i-7lq{K=h!Maf|8X&Kt_H^esM;A zfr4|enSzFIVsd64NI(;87*Lb1ua!%Fa%paAUWuoRtrAc~FC{a@%F)ur$jr>u($Lby z(9qD;%+kWi!o|ed+0xm~)WyWu6-m2iUU5lcUUDi-dnQ7=1zzotptAycu-G?0B^Bsq zu&KZ>0E+>+pme2R19fF;QD#|cid#{BE;u!TW6mlCx7*F}TCM=mpNZS?W;pfhgVVb{ zD1jp-cu+n9ivcqdFh}acvm}rM&(x`Tz?@tJ%(A=J$sPli2ThsI&H=?i?oJAxuFgOk z&CGO-^b8poG$xi#-0$raD019BD{w|)fOB(mhiT}P#w(W=OkuscfbDP8m#6&)<omx* znQ(1Es76QBFVidW0ofO1SX>2NyuFJ9y^p@$BQoVgvE}!gci-<7@Aw|G!Ohq!EMKZ6 zXWfEUXU6Y;4lUaKbY@!2jgJp{G<R7E&+NE#!S3CL86TJ3Uvu@xo&DFgO}ZeuVGCEk z=BhA(c_Q=Tv{$I*bC+wrywUYxWx;$kaglwdUn^SUef&ayoKDe|;%nOKd}Hpa#}%^L znY!9K`p0LV6`A^~_RMq(p`ANBT>5u#&oxkF+A%X}jzV3V#MGPq_I7M3uFZcA-v1N% zY0~NwYl57Tr;229ed&yv@`|(5^P;ek(Udd){=Z#4Pv%?bDr=?-e^Mm!Z7*zMy`Osc zhmA?kd*uzbhg{c9n{VGRvHap`)3dwz&+%I9UX%Gz*5ZE4J9g7$N9$B)?=YR&a$Pth zzTsT<2K~^gu)EKH$Sr#PDKvb;ZuY%J_jdkaj$lf^pYdkFSzzJIS>O>_%)r1c48n{I zv*t(u1xr0$978JRygkpz%b+N5V8ipj)pjusMp3M{XU%-C`u}aiWuEJj>x^U4cbqGj i`_SreY1?bc1*BiSW1f3Jq&N;#CwRL0xvX<aXaWFyCQNJq literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/NextDialog/background.png b/resources/skins/Default/media/NextDialog/background.png new file mode 100644 index 0000000000000000000000000000000000000000..bdd2689e824baa75c9db0f00698dfc74b2216577 GIT binary patch literal 2088 zcma)7Yi!e26n4kTqo`t9%Zm8HjEfMD#gDW}iIvc$cG_m(5}H8MjtN}bX)bN-U^^r& z3~gmRI`OLL7(-0FhQvc`ViJ2WMmtys6Qb1^ph-xeiiu5Ifq}%v7>HdvX;PrD{cvL6 z?>pZ;=R4QOZN93C+???_2!iB#E7@xJZ#Axw!{P6Q(zS;XB>Nf&1oa>{n--J^&WlRD zgeM|Vh$D!zG!f;6MoCBOr3N6ou$y0XVki(@SdEP%xhNxrL1l|7`CF<2LQA9I5V6vk zInD$P1tOBpqlrk9tkDS<X6n-L-dH9u)THQ*E-c9TP)1QD)P~z|5}TQWI#n@5SF`1* zVEE+1!nz)%2_hbk<MAR~Q5y)#;cyV7m9SbZkYUl9Wt~r0WUbJUNNTW>Ca55)14Tv+ zO}<`<=`IX|IGPgYV=%XrxU8kBhdvNe0)z+(Cke<P21gixCzuWrK$OQ9M>G7vTpffm zxj!OrAg26aIy5b<`v^P1M~5PF+_UMC$Rn9+x@RdYf2e8)HqZjiQHiLQG$p1Al4q$T z>xF|eG>RWuA??>-dNi!)iWXL$QdSzy6bId=XG^k{>Qd5dbA?kxsVTK=xJP><e1inJ z4SLvx*+_HW3{V5a$JdRrnqtOjm?r~hgf$Zq%wR68#0t-$aYjtF+94s>Dw<IxURTtB zqQJb|#(RVM1`0+Qza5>z@dA(y2d0{OMi49mgqm4imtX><m9&tQg|Y`Ik|s&I$UdE< z;7x`NUlx(q`Txo1IGR<2SOm7s%epYQA1(rsb~{+B-R^Nv4wj-QkKHk&YzAA*Fb<~N z#ugWO2DXFYURl$5S&#<8K_-00GsBHe(LRunnu08V324yfbXziD?NdNTX;M@_oHfuE z%FHKyIA{|y+V8@O9)r9L<5QIoXo8qlT~UU5J&c`l*zFc8PML!MbwU?lVU<9Gy=pdQ zLuxwsr6tLy<gjQ0CWNBqYeMsaaZp@XR8_>7Ac^M2Ff@s3N=T3Ms^o5fR>S6pLI57A z=4Ag-3NOKvnWh8cVF`*oz2QVEF~XQr1DQFACvtb1gh7K1#wFL_z=V^RNRM9lkRINW z3@5e<NAsGJE$<>oRx5zMJ>eIEOlgm`cg`xCn}4cwaeGI#k7ZWAV*?%T^XGPUWM9}h zyw-B>cE!#=6LUs^+`3h*`}Qtm9%LUGe%9^FcNg1=3yuVi_5Ap1oAdo;H-1@h)3Ih* zZ+7FPJ>s3!S*NevuAA~}{A@+R0<Je0KDl9Y&(_@si~8C|#;#wj+TAT53mv+*<YC<z zwXCMAzF>ksU?H4SonLRM;V!>)!*}km)aU0nPA<qlZo89MwQzRTyt`*a%ZoX09pQd@ z??7K7cRH|4e}y>p;o1C-EB|aImP}GLzIK_obWa|(_T>Wqy1YeO-R0e5u69>+_V3$V z>g(Ad%~QwTn&v5<U)er+W5xN_z?{?D@?8@PnIHNeUOE2T_Y)WHJ$iD?`Z=p_T^!B5 z{>K~Eb-m2VyEC7=RJh>h1AiYJLEgq&_8<82uU)ZWJ@~=8<s*7ZcKz;N4-S7SZU)@s zzB|uvpBwI6eXaYo-W9!$<7-YIU4CI~{|)z!Z$HN$AbBerx~3h{--I98QPIj;4M9fd z8CMpvZTmR5Sm$Nkf$-bcd$UG$kuP+0eK!B{rTeq?wS9Bq3;(A651KEI+5X9}%k>|9 rSGm4@e4(v;@w)Tl_EXiue=cZQn}n>Q6ZhS7jSsxnQ^kH<wy5=AQIo*F literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/NextDialog/close-fo.png b/resources/skins/Default/media/NextDialog/close-fo.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e4ece7835837b3bd8bff583cf180f42f752848 GIT binary patch literal 11255 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 NWhIp)s>O_h{ttfX@I(Lr literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/NextDialog/close-nf.png b/resources/skins/Default/media/NextDialog/close-nf.png new file mode 100644 index 0000000000000000000000000000000000000000..aa61c1eb808c378969e88f3532bade5eee5326db GIT binary patch literal 11226 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 literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/NextDialog/play-fo.png b/resources/skins/Default/media/NextDialog/play-fo.png new file mode 100644 index 0000000000000000000000000000000000000000..969dfd90063d5d81e92ccc9e118449eb0636d576 GIT binary patch literal 1823 zcmah~eM}Q)7(YG(Sk#S(RkwLL7ZF%{cg4zjp-@KAcB8d}%16RxkKS8)q3xY_S84s2 zP6Z^8$REm(FlCWA_T_LN&I~szVH1NI7X3rBY<@(H&Z%>o$rg3ndspOR;MU~Yd*0vg z`MuBkJkR^I*<oLmnzAqjL6B6d#as%13*oaYDIR{$N?2QsAn_lw&Pu7$RzkTszm{gW z8lVmNd59xOUO|Yb-E}}hYd|d<Fk*N6k7Fpy7_kbyjj-`Wz{^@T2%v0(-Ra&?=Qc1{ zL4HbJh=KxsAkk>Z?+b`j$cQPr6nvMLaST-`Qk@a2v^mfsP5`K0tJe}(ehQi=FdnMZ zymHJLT1Lz(Nj!z)_4W1I`W!7M)Z(PUV896-uG48CLnDR*5*^Y6#3iysRKpBJx4`le z%LPzbldj={k`cooj*f{-tY<<!AdVd$CIXicoYWHdWI713PoT@lG&=9)Bu?~l{P+Q; zY%vZ`bA;)e*(F5tGm&k!|EBx>Q;s1@#p_`r#*#WErpFtI&M*(~QXq0c!3~PngMhR| z$%LF2%Ic?UK?w-dN?s$jocI?|!7}pt3jM^oEaVHyl~@yC;nundsB!``V!1ll19E+o zZK`I3<Wy4*HUxZ?X4Ve^B2*pUZ33*C1dfHMfJK~-6Uty)1KCNR6OD4b7L<b9I0nL` z63PT*Eh=&doQL&6Y?T1QqMP;s)U1z&42woVKh_gA(-LIp2pypzNDaBdNxn#tITWGK zCP<2y#D;f)p(XnN_;N?1c~IYGqb#f_aY9&Gine(fSQoXTrjJ$n8%32WQ?y&ox)I|A zjtRN}qZ|p!MNyISNcFS;3Tt73jaZ?_!@~N6QHR{QD5=fW=1g^{_W~&P<fGwZf#LFn zdmLFFJ}ve<g)r%cLci!5y!mj1;Nvp{8sk$41mNfq;EWly{F(^o(~m5y$dpPTh<cCP z<!U`;J*)AMF4xeR>`Y;f(Av7QwbZp`AToqTA}1olXnRL>_2orBrw?pR+ti=vOpSjl zSm?P@-ZZbTrzbrl?xx1%Yp-@VV|y+khgXNKF>PnOF-!~76mN*Tm~T2_3fNhl6CWzy zcmCLj3)LhH>Qr}B#j5vIdsN@3PN|wz2Gfz)?X$LL#tm(`aDTzp`+t(x;?^vUI~Ch~ z2Wu>8lp0lyPbWUx?adrn-ezE3pO(FOuzcG<(hqkNnv0wHwd8_DamX77bq(rszn_IC zP3A3CA{^Oi@)Lt}cBv60<^`*{&{^5wK5ucWU6JJ@v(>?Y-n5l(6G9g{m~~PgvA12V ze(fekcH3R9%W1bhI(=@DBZDCN)-fL2eWvCa6Ep~dO7K~p0*fuO(cAeUuAl#$%t{(Q z+xxOZao~U1oxYxOt)3@kcXo6<#460c^{Kkk)bJzubLX@7QhTBC{O<V`4MTI}SGD@D zckUe?Ox2ud>A#qBX!Al-Ms<GIXIF<8ORs)uxN_<Ene6vxKYFk$vnV6u;U7Bs+kx*d zjNZ7q<BMG_@9y8!@=E(``_auy!{6Q7e{__+z2onVxz}$;+GB5A+p)Ib`sux!_O@j$ zq^_JB{)yfA`rPu9mJjxQJ~w;LtpnU(Z%@^-V-FtP$z7M#Bo@@1Pd;E*2V=ts^3}*n Va&Gb`U(5e%)?&N)&`Yju{{T<NX1@Rc literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/NextDialog/play-nf.png b/resources/skins/Default/media/NextDialog/play-nf.png new file mode 100644 index 0000000000000000000000000000000000000000..1c12b92ea86df8f48b164abbbe3b20b0d834a3de GIT binary patch literal 1814 zcmah~4NMbf7(OT>V%24Arp}OE&P7JFy}LryURo%lC|xKkP_-};dR)KK3vKVbyV{m8 zq@p;K7?Vw02ux!b(=6)DG8caq%8waj)49z!jc)!-<4=s5P2=3y+;>;xXX@7Edi}oV zd7tn7``)%WN>|KEe<dA3kXg23YdL%uz~}X}6!<-JT1_>Aq#R(KF3Dvtp%^Zpqdi<L z&_x0~#1X`lAK_`H9!O{{sAGd>?C#M%3}roLY_-8o*!e=>V~ZOEP|;B8WE$!jqX)~+ zOE*O*C=dV=jYb0gph!i`n4(L;-|{w&p$bK+H)Afl11;nPfEsiL9f9Sgqb9-QrOK_# z$Bg01jQJ#qr*J$R4(q}>I!>s=Nu$w-6M9^)*FuI?j0Pn-q78}*WQl}^6^M+$@)FAh zQCX9&<wBAf!yt~1iA$_^LOm#sg%1;f%Lq>D2z)XfMA+xhW#lP3@8cv+^l|)n08@?_ zho3Tp>3g<Ggyx?`w%h-k9tcbcLzIfv!bFTEb&5|<4iKGD9^mCb<U#@iiq?Xlv_Q#( zoEOR#pzA;h2-ZnHGnPyI3s}v1<ozXviG5kfACfDvGRVTC^%Ib50yASv^{@xz`Y6X# zJ`<8tO*z;I@Vl&R00c#-I)2&&SUCwCb5Q{cIX@><z_tdmks&AH<#;V91-Ek^2$PB{ z6OgU2&>?VM)(^220*HzX?FXpUkZ>6mje>TpH)^FN$j}pdLQ9ZZa*2~%Mv*y`o?Jwb z6fubn?}CSx=>Ox(9gUixzTHk0v!cWaQDrO9<`rN~VwG6>S*5>GRH-sWGji6=7%y<1 z5Cc3)NKh_;ikw#p(*h`{g9SEY1zs-;>k~yCa_6F?ZmBM3szKNXpxE<6!^a%M<qP-3 zGZX%l?<R#X>4rkP#2&o)aD?FFGXyTirw|Cj(Idba^IO;ELvTL*%)*LHsRV*(wli*b z`-ttN)=RqG!zUJH3v-0__IKOM-RlP8!)QF-AO9Nd_@KJ_()`<V2bwb)kES|jrR)e5 zcrUNo^zx@YJ#({EH?<ajN43M5+;b5*_*T@GwC#j1$+N|?DaEKdmuER-36`>YCqBID z!_&u3xl!%3A-(#Jx=6iOy<L4=J)&+?8!d;DTV}RotA^K|eK4>2!6WjjYUM)JNb;^b zSZhhE)T(ZMF*S3SFZ)#PHY4l)tRmLCYW+akPj{!a6}9m-<h=PRq^VcGQS-z7lQ7a5 zd~u}+M|Ot%Bq5z$8U#sNV6zrDT^pIx#f-)s&pkC;6B^i)vAmlQy5jw>#i@AdrMi6w z%dX9HWD&$Y;rjVyo9FDw&}dwhSjjKW>8EF?;pfaPu)O*k)vc7&RIs<IzVc*JC|T9= zLe1WNhwn90u~@8I+mAVEOLsgTAG)#oy#rsh9?v_{v9(GtbnL#Z*sbY*^X+$HvYkpb z*!Or(<J>>09{>8+)tm$CX79?_Z|-cm_G7+OcEou3;^Qw@pD_(BEX>Lp9sT_N?U66e zUb}H+LwILzchhL*j>VZ>{KIqaZ@KvQm5z@OXKcOTYMkDe-br14xYKZb_A#tyb8G4E z_d{oHp07#!gy~uNZD&hc%cDWA*7^P7mzqZZjFqL)(+3Z~O5WN~meo$hOt3{hve1^Y S?=HxHXSSkJ>;5;~>;D0lTyUZQ literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/close.png b/resources/skins/Default/media/Shortcut - Copia/close.png new file mode 100644 index 0000000000000000000000000000000000000000..0a144666ef760af7c275b8fe8a2b249835343940 GIT binary patch literal 874 zcmV-w1C{)VP)<h;3K|Lk000e1NJLTq002|~002}71ONa4wqn>y0000WV@Og>004R= z004l4008;_004mL004C`008P>0026e000+nl3&F}00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_000McNliru-w6c(90DB+s%QWJ0?$cAK~!ko?b_c?6G0RP z@UI0gKzIWYQia_Ty;lj|Xkt>5@){<X=o9IM8lM2XK&)?}{X;`ysm4n!HG$&ig|W2D z?#$WQvo?)$de=E;_Lu3*c4pfGF&$ByG{8eW)I&YgLp{_(J=8-zbQ(enx`_Eq^;fZ& zKlDD{p@lkLV%e#F1+VZ4P3&XSkj1R8Do^+~nhaCdhinb&PgqUL%gWqlpQCY@k{a@G ztiI3fJfPJGlO$UysH3BKL9J>-H)EJ-Mm0K`7t{?+XpuE0g@l^KB*_C7^`Hy<MmkeX zO!6xiq80A9r(ZZTw3e`oFtIhnE=q>#0nVduxkLN-U~Dg8FM%3j&&UL7_<8BQ42$VH zn>0r|W&(t4TdL|@(p~l7YRv5=N)5&;0s-@e_U%bZT_0wzyE3ywCDbmT@)mFN8NawP zHJ}nIIpDyWV_@n)WmH=<OiV3kc52<s)PsU+8K@23wDqqCb*I+tOkF6rX8j6qWNJgx zQ-w@@C>XFKW`5?Xv|oKFxFd!>miL;+x&A!{qP#QI<elaLE##1y3-lpoMR`~7AVsh0 zGqlXEc#a;kJgLQGJ_}tA@pFL5GE-1>nYL|le6%Ih$uosiEoR{Gp^s01;9o$qjHYl@ zbw<<9Jmsl?8srJJGdtX2$TFYs*qxd`voW$m3Q|7@P=ZRB<7c7tk1Q0RE7p}Bce1n} zSXx}xg}$*|FlqO0r@q#N&RaGgrbCA9z|!Gdw8nj#SHz5=wSfv+hSn`ZD=-`5avsql z51d9*GOGJ5XwK1lBfl`EWtylzct>|Jft47l_yC+h?eS4uZZ;^m6``hK#!(ZP8egFm z_J)`Fn3DO2+8EY<Ph#q9N;deGT^{i{uQ}B(@dc0A<sR=QXBCwG=(>ROm|rsFF^3uS zObJ@h+~dFd2ZV=usE1A(;GrJsp&sg?9y*=S-{)xseoD+zK>z>%07*qoM6N<$f;I4l ACIA2c literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid-white.png b/resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid-white.png new file mode 100644 index 0000000000000000000000000000000000000000..225aae63aa703a6ddbf30bdffd0893f28a469996 GIT binary patch literal 3774 zcmeAS@N?(olHy`uVBq!ia0y~yV1C2Ez!b#61{8_R^S1<2jKx9jP7LeL$-D$|SkfJR z9T^xl_H+M9WCij$3p^r=85sDEfH31!Z9ZurBiPf$F{C2y?R7&&1_ho28yf4+A8725 zI~mLy`&<&JNg?$=6OdADlLeAZJk~&xMY0S?au@+^XAnra!3b1zVgoY+gTsVGb_Rw9 zg~NP63CB?lqoFjKG)D8oXsIz;RF2jUqm|-l(_yr|G}=fUZ5faDABK0Q$<kQ-IXB0G U`faxt0TVler>mdKI;Vst0FW=fA^-pY literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid.png b/resources/skins/Default/media/Shortcut - Copia/dialog-bg-solid.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c13ebd70be0fb9b4026fa4bac916a9ffbaefc5 GIT binary patch literal 1891 zcmeAS@N?(olHy`uVBq!ia0y~yV1C2Ez!by*6kw=f6?h7y6p}rHd>I(3R2di=ni&{= z{s+=885l|p7#Ln9FfdrnU|<l>pA>)84XBnW$=lt9;eUJonf*W>XMsm#F#`j)5C}6~ zx?A@LD9B#o>FdgVpOckAhB=F8);|UYwiBK%jv*Dd-d;9jWH8`q*wD4IzPEIvtO{4f z3|66M<qRL%ZYmynBam3uVPNetLssbwpI}-$$7bi2QQ^@r8BGwQxnZ;@7%d(LzHT_e ZxUXPMm(Qa3Tfl0L!PC{xWt~$(695=D%^d&$ literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/logo.png b/resources/skins/Default/media/Shortcut - Copia/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d8a680fe8fe6a19175d45ccf88ff2c05022c0395 GIT binary patch literal 838 zcmV-M1G)T(P)<h;3K|Lk000e1NJLTq001=r001-y1ONa4fU~$>0000WV@Og>004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_000McNliru-w6c(90`iqbKn2~0;@?xK~z}7z1BNV6j2nw z@w0#tMdh6+z8Zsyv9tlBpq*AmqpYAt5=6mZWkZAy;0x>wozGaI2#66PvZ9neu*9ez z0V5W;@V8)v*|~Ec>&$Y>xo6IA?zxX$k&J_Ornt+R%-_ru>S8elvy9QnatyFGd(bt~ z<9U{5ra%DwS-|KZIG&NeGSDKnIy&eo=6D(rW`zLyB3LtakaVD+mzcRyj@8;OxlZ47 zLJGoCI8MJ$X2Uk{fK&VW?Kg*gRxU9h@!0Q`pa!Mh4n;u?$;otxTq+#jrP|wf%UTWj z*NAN-_(d<P?9dgGR69~H@NICm@+CF)m^?Go20)z?6$DLr#vh?Sk+h_eY8|Ly)+x|u zU(Q1fM)OjNoqDN2Tlf@^_aLB+G%@c?y;7jfeALX|k8#ce*{BiLJ3|2XJ#D1XcYB+t zQ3cv$guEDW<!3@qjj+K7Rqz3O+#^qjGp7vbs8I#VW!M1%=*W?yqMqyE>*}=}kNxyL z%a?uP7W;GLrc5i3gMLrP<XCJ*Nt~4)CumN(MTAPGeUm-xY0*Id-cYEk!gXu&<~a2) zxstD1ss>0<Wcp-oSfF}u@=m^KsA9S(vGs>rTIf$2BqG8t4_0R^TR;Ofj#!d-P>J13 zSgipnX9Ae#vNCczU`D5PWvcjYrYL7}sgR0w`=AC>bN289IHVaZ^FYB^j|XbBE%06@ zsR|q^<-O9PHYMv$YK_i_JqWxj0wI7_C9|C;9C3g&d?^#!?R@FK%Vr1xT#O1T;Vrgk zi2^DjVTE9U^WmvZ2MAz>jpj=BKhg`X-xPjUg^W4P>TGCAgkr|6E7cqUl)@55=t4wL z3e#`+C^mW;R-!q^Q61kjtdw^v0*Q!F=EGXU*EC_v_j_CTdNynvNM;WG1q3XOBXVoM Q{Qv*}07*qoM6N<$f@PO&A^-pY literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/white.png b/resources/skins/Default/media/Shortcut - Copia/white.png new file mode 100644 index 0000000000000000000000000000000000000000..528c66f6e8ed820dff50fb927f805aebd269327b GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^EFjDXBp4dI7GDNZ3dtTpz6=aiY77hwEes65fI<x~ z7#K<o7#Ln9FfdrnU|<k0n6oR;1}MRl<n8Xlz<9SycnOflS>O>_%)r1c48n{Iv*t(u z1=&kHeO=k_bFwnXa0y>fiUJDpc)B=-a9mIRae$$XfuDo<U|jU$gFtZxPgg&ebxsLQ E0Oo5Y8vp<R literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/Shortcut - Copia/white70.png b/resources/skins/Default/media/Shortcut - Copia/white70.png new file mode 100644 index 0000000000000000000000000000000000000000..d428a73af228f6e940998d8f228086f6f61f200c GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^EFjDQBAI`LeFahq$sR$z3=CDO3=9p;3=BX21L>Cx z45bDP46hOx7_4S6Fo@?*ia+WGRLhj)?e4<xzrFv=ejtytz$3Dlfq`2Xgc%uT&5-~K zvX^-Jy0YKrWMz=y6271m1r!qVba4#fxSsrH|I9`(=xk&ZXJ)VtXWVt|y3<>r3I<PC KKbLh*2~7Z(Yb?D0 literal 0 HcmV?d00001 diff --git a/servers/akstream.py b/servers/akstream.py index 72fb5ddb..cb0dfd7a 100644 --- a/servers/akstream.py +++ b/servers/akstream.py @@ -37,7 +37,7 @@ def get_video_url(page_url, premium=False, user="", password="", video_password= i = 0 for media_url in matches: # URL del vídeo - video_urls.append([vres[i] + " mp4 [Akvideo] ", media_url + '|' + _headers]) + video_urls.append([vres[i] + " mp4 [Akvideo] ", media_url.replace('https://', 'http://') + '|' + _headers]) i = i + 1 for video_url in video_urls: diff --git a/servers/cloudvideo.py b/servers/cloudvideo.py index ef66b57f..d664b88b 100644 --- a/servers/cloudvideo.py +++ b/servers/cloudvideo.py @@ -9,8 +9,10 @@ from platformcode import logger def test_video_exists(page_url): logger.info("(page_url='%s')" % page_url) - data = httptools.downloadpage(page_url) - if data.code == 404: + html = httptools.downloadpage(page_url) + global data + data = html.data + if html.code == 404: return False, config.get_localized_string(70292) % "CloudVideo" return True, "" @@ -18,7 +20,8 @@ def test_video_exists(page_url): def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.info("url=" + page_url) video_urls = [] - data = httptools.downloadpage(page_url).data + global data + # data = httptools.downloadpage(page_url).data # enc_data = scrapertools.find_single_match(data, "text/javascript">(.+?)</script>") # dec_data = jsunpack.unpack(enc_data) sources = scrapertools.find_single_match(data, "<source(.*?)</source") diff --git a/servers/decrypters/zcrypt.py b/servers/decrypters/zcrypt.py index c0e944fc..d5181352 100644 --- a/servers/decrypters/zcrypt.py +++ b/servers/decrypters/zcrypt.py @@ -20,11 +20,12 @@ def get_video_url(page_url, premium=False, user="", password="", video_password= patronvideos = [ r'(https?://(gestyy|rapidteria|sprysphere)\.com/[a-zA-Z0-9]+)', r'(https?://(?:www\.)?(vcrypt|linkup)\.[^/]+/[^/]+/[a-zA-Z0-9_]+)', - r'(https?://(?:www\.)?(bit)\.ly/[a-zA-Z0-9]+)', + r'(https?://(?:www\.)?(bit|buckler)\.[^/]+/[a-zA-Z0-9]+)', r'(https?://(?:www\.)?(xshield)\.[^/]+/[^/]+/[^/]+/[a-zA-Z0-9_\.]+)' ] for patron in patronvideos: + # from core.support import dbg; dbg() logger.info(" find_videos #" + patron + "#") matches = re.compile(patron).findall(page_url) @@ -51,9 +52,10 @@ def get_video_url(page_url, premium=False, user="", password="", video_password= continue else: from lib import unshortenit - data, status = unshortenit.unshorten(url) + sh = unshortenit.UnshortenIt() + data, status = sh.unshorten(url) logger.info("Data - Status zcrypt vcrypt.net: [%s] [%s] " %(data, status)) - elif 'linkup' in url or 'bit.ly' in url: + elif 'linkup' in url or 'bit.ly' in url or 'buckler' in url: logger.info("DATA LINK {}".format(url)) if '/tv/' in url: url = url.replace('/tv/','/tva/') diff --git a/servers/mixdrop.json b/servers/mixdrop.json index 6d39077a..6977131e 100644 --- a/servers/mixdrop.json +++ b/servers/mixdrop.json @@ -4,7 +4,7 @@ "ignore_urls": [], "patterns": [ { - "pattern": "mixdrop.co/(?:f|e)/([a-z0-9]+)", + "pattern": "mixdrop.[^/]+/(?:f|e)/([a-z0-9]+)", "url": "https://mixdrop.co/e/\\1" } ] diff --git a/servers/supervideo.py b/servers/supervideo.py index 4ef07daa..20410941 100644 --- a/servers/supervideo.py +++ b/servers/supervideo.py @@ -9,7 +9,7 @@ import ast def test_video_exists(page_url): logger.info("(page_url='%s')" % page_url) - + global data data = httptools.downloadpage(page_url, cookies=False).data if 'File Not Found' in data: return False, config.get_localized_string(70449) % "SuperVideo" @@ -20,27 +20,35 @@ def test_video_exists(page_url): def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.info("url=" + page_url) video_urls = [] - data = httptools.downloadpage(page_url).data - logger.info('SUPER DATA= '+data) + # data = httptools.downloadpage(page_url).data + global data + code_data = scrapertools.find_single_match(data, "<script type='text/javascript'>(eval.*)") if code_data: code = jsunpack.unpack(code_data) + + # corrections + if 'file' in code and not '"file"'in code: code = code.replace('file','"file"') + if 'label' in code and not '"label"'in code: code = code.replace('label','"label"') + match = scrapertools.find_single_match(code, r'sources:(\[[^]]+\])') lSrc = ast.literal_eval(match) - lQuality = ['360p', '720p', '1080p', '4k'][:len(lSrc)-1] - lQuality.reverse() + # lQuality = ['360p', '720p', '1080p', '4k'][:len(lSrc)-1] + # lQuality.reverse() + + for source in lSrc: + quality = source['label'] if source.has_key('label') else 'auto' + video_urls.append(['.' + source['file'].split('.')[-1] + ' [' + quality + '] [SuperVideo]', source['file']]) - for n, source in enumerate(lSrc): - quality = 'auto' if n==0 else lQuality[n-1] - video_urls.append(['.' + source.split('.')[-1] + '(' + quality + ') [SuperVideo]', source]) else: logger.info('ELSE!') matches = scrapertools.find_multiple_matches(data, r'src:\s*"([^"]+)",\s*type:\s*"[^"]+"(?:\s*, res:\s(\d+))?') for url, quality in matches: if url.split('.')[-1] != 'm3u8': - video_urls.append([url.split('.')[-1] + ' [' + quality + ']', url]) + video_urls.append([url.split('.')[-1] + ' [' + quality + '] [SuperVideo]', url]) else: video_urls.append([url.split('.')[-1], url]) - video_urls.sort(key=lambda x: x[0].split()[-1]) + + video_urls.sort(key=lambda x: x[0].split()[-2]) return video_urls diff --git a/servers/vidoza.py b/servers/vidoza.py index 9ec0ccad..f9806ec5 100644 --- a/servers/vidoza.py +++ b/servers/vidoza.py @@ -8,6 +8,7 @@ from platformcode import logger def test_video_exists(page_url): logger.info("(page_url='%s')" % page_url) + global data data = httptools.downloadpage(page_url).data if "Page not found" in data or "File was deleted" in data: return False, "[vidoza] El archivo no existe o ha sido borrado" @@ -19,7 +20,7 @@ def test_video_exists(page_url): def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.info("(page_url='%s')" % page_url) - data = httptools.downloadpage(page_url).data + global data video_urls = [] s = scrapertools.find_single_match(data, r'sourcesCode\s*:\s*(\[\{.*?\}\])') @@ -30,8 +31,8 @@ def get_video_url(page_url, premium=False, user="", password="", video_password= if 'src' in enlace or 'file' in enlace: url = enlace['src'] if 'src' in enlace else enlace['file'] tit = '' - if 'label' in enlace: tit += '[%s]' % enlace['label'] - if 'res' in enlace: tit += '[%s]' % enlace['res'] + if 'label' in enlace: tit += ' [%s]' % enlace['label'] + if 'res' in enlace: tit += ' [%s]' % enlace['res'] if tit == '' and 'type' in enlace: tit = enlace['type'] if tit == '': tit = '.mp4' diff --git a/servers/vup.json b/servers/vup.json deleted file mode 100644 index 4c468694..00000000 --- a/servers/vup.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "active": true, - "find_videos": { - "ignore_urls": [], - "patterns": [ - { - "pattern": "(https://vup.to/embed-[A-z0-9]+.html)", - "url": "\\1" - } - ] - }, - "free": true, - "id": "vup", - "name": "VUP", - "settings": [ - { - "default": false, - "enabled": true, - "id": "black_list", - "label": "@60654", - "type": "bool", - "visible": true - }, - { - "default": 0, - "enabled": true, - "id": "favorites_servers_list", - "label": "@60655", - "lvalues": [ - "No", - "1", - "2", - "3", - "4", - "5" - ], - "type": "list", - "visible": false - } - ], - "thumbnail": "server_vupplayer.png" -} diff --git a/servers/vup.py b/servers/vup.py deleted file mode 100644 index 9eba027d..00000000 --- a/servers/vup.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# -------------------------------------------------------- -# Conector vup By Alfa development Group -# -------------------------------------------------------- - -from core import httptools -from core import scrapertools -from platformcode import logger - - -def test_video_exists(page_url): - logger.info("(page_url='%s')" % page_url) - data = httptools.downloadpage(page_url).data - if "no longer exists" in data or "to copyright issues" in data: - return False, "[vup] El video ha sido borrado" - return True, "" - - -def get_video_url(page_url, user="", password="", video_password=""): - logger.info("(page_url='%s')" % page_url) - data = httptools.downloadpage(page_url).data - bloque = scrapertools.find_single_match(data, 'sources:.*?\]') - video_urls = [] - videourl = scrapertools.find_multiple_matches(bloque, '"(http[^"]+)') - for video in videourl: - video_urls.append([".MP4 [vup]", video]) - video_urls = video_urls[::-1] - return video_urls diff --git a/servers/vupplayer.py b/servers/vupplayer.py index 113a30e9..e348a184 100644 --- a/servers/vupplayer.py +++ b/servers/vupplayer.py @@ -7,6 +7,8 @@ from platformcode import logger, config def test_video_exists(page_url): logger.info("(page_url='%s')" % page_url) data = httptools.downloadpage(page_url) + global data + data = data.data if data.code == 404: return False, config.get_localized_string(70449) return True, "" @@ -15,8 +17,7 @@ def test_video_exists(page_url): def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.info("url=" + page_url) video_urls = [] - data = httptools.downloadpage(page_url).data - logger.info('VUP DATA= '+ data) + global data patron = r'sources:\s*\[\{src:\s*"([^"]+)"' matches = scrapertools.find_multiple_matches(data, patron) for url in matches: diff --git a/servers/wstream.json b/servers/wstream.json index 99428674..72b1acbb 100644 --- a/servers/wstream.json +++ b/servers/wstream.json @@ -7,7 +7,7 @@ "find_videos": { "patterns": [ { - "pattern": "wstream\\.video.*?(?<!api)(?:=|/)(?:embed-)?(?<!streaming\\.php\\?id=)([a-z0-9A-Z]+)(?:[^/_.a-z0-9A-Z]|$)", + "pattern": "wstream\\.video(?!<).*?(?<!api)(?:=|/)(?:embed-)?(?<!streaming\\.php\\?id=)([a-z0-9A-Z]+)(?:[^/_.a-z0-9A-Z]|$)", "url": "https://wstream.video/video.php?file_code=\\1" }, { diff --git a/servers/wstream.py b/servers/wstream.py index 4993fb75..f903e8b7 100644 --- a/servers/wstream.py +++ b/servers/wstream.py @@ -11,8 +11,21 @@ headers = [['User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20 def test_video_exists(page_url): logger.info("(page_url='%s')" % page_url) + resp = httptools.downloadpage(page_url) global data - data = httptools.downloadpage(page_url).data + data = resp.data + page_url = resp.url + if '/streaming.php' in page_url in page_url: + code = httptools.downloadpage(page_url, headers=headers, follow_redirects=False).headers['location'].split('/')[-1].replace('.html','') + logger.info('WCODE='+code) + page_url = 'https://wstream.video/video.php?file_code=' + code + data = httptools.downloadpage(page_url, headers=headers, follow_redirects=True).data + + ID, code = scrapertools.find_single_match(data, r"""input\D*id=(?:'|")([^'"]+)(?:'|").*?value='([a-z0-9]+)""") + post = urllib.urlencode({ID: code}) + + data = httptools.downloadpage(page_url, headers=headers, post=post, follow_redirects=True).data + if "Not Found" in data or "File was deleted" in data: return False, config.get_localized_string(70449) % 'Wstream' return True, "" @@ -24,20 +37,7 @@ def get_video_url(page_url, premium=False, user="", password="", video_password= video_urls = [] global data - if '/streaming.php' in page_url or 'html' in page_url: - try: - code = httptools.downloadpage(page_url, headers=headers, follow_redirects=False).headers['location'].split('/')[-1].replace('.html','') - logger.info('WCODE='+code) - page_url = 'https://wstream.video/video.php?file_code=' + code - data = httptools.downloadpage(page_url, headers=headers, follow_redirects=True).data - except: - pass - code = page_url.split('=')[-1] - ID = scrapertools.find_single_match(data, r'''input\D*id=(?:'|")([^'"]+)(?:'|")''') - post = urllib.urlencode({ID: code}) - - data = httptools.downloadpage(page_url, headers=headers, post=post, follow_redirects=True).data headers.append(['Referer', page_url]) _headers = urllib.urlencode(dict(headers)) diff --git a/specials/community.py b/specials/community.py index 9dacf7f6..4eaf0532 100644 --- a/specials/community.py +++ b/specials/community.py @@ -547,11 +547,9 @@ def episodios(item): if pagination and i >= pag * pagination: break # pagination match = [] if episode.has_key('number'): - match = support.match(episode['number'], r'(?P<season>\d+)x(?P<episode>\d+)')[0] - if match: - match = match[0] + match = support.match(episode['number'], patron=r'(?P<season>\d+)x(?P<episode>\d+)').match if not match and episode.has_key('title'): - match = support.match(episode['title'], r'(?P<season>\d+)x(?P<episode>\d+)')[0] + match = support.match(episode['title'], patron=r'(?P<season>\d+)x(?P<episode>\d+)').match if match: match = match[0] if match: episode_number = match[1] @@ -561,7 +559,7 @@ def episodios(item): season_number = episode['season'] if episode.has_key('season') else season if season else 1 episode_number = episode['number'] if episode.has_key('number') else '' if not episode_number.isdigit(): - episode_number = support.match(episode['title'], r'(?P<episode>\d+)')[0][0] + episode_number = support.match(episode['title'], patron=r'(?P<episode>\d+)').match ep = int(episode_number) if episode_number else ep if not episode_number: episode_number = str(ep).zfill(2) diff --git a/specials/nextep.py b/specials/nextep.py new file mode 100644 index 00000000..e2403011 --- /dev/null +++ b/specials/nextep.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +import xbmc, xbmcgui, os +from platformcode import config, platformtools, logger +from time import time, sleep +from core import scrapertools +from core import jsontools, filetools +from lib.concurrent import futures + +PLAYER_STOP = 13 +ND = 'NextDialogCompact.xml' if config.get_setting('next_ep_type') else 'NextDialog.xml' + +def check(item): + return True if config.get_setting('next_ep') > 0 and item.contentType != 'movie' else False + + +def return_item(item): + logger.info() + with futures.ThreadPoolExecutor() as executor: + future = executor.submit(next_ep, item) + item = future.result() + return item + +def run(item): + logger.info() + with futures.ThreadPoolExecutor() as executor: + future = executor.submit(next_ep, item) + item = future.result() + if item.next_ep: + from platformcode.launcher import play_from_library + return play_from_library(item) + + +def videolibrary(item): + from threading import Thread + item.videolibrary = True + Thread(target=next_ep, args=[item]).start() + + +def next_ep(item): + logger.info() + condition = config.get_setting('next_ep') + item.next_ep = False + item.show_server = True + + VL = True if item.videolibrary else False + + time_over = False + time_limit = time() + 30 + time_steps = [20,30,40,50,60] + TimeFromEnd = time_steps[config.get_setting('next_ep_seconds')] + + # wait until the video plays + while not platformtools.is_playing() and time() < time_limit: + sleep(1) + + while platformtools.is_playing() and time_over == False: + try: + Total = xbmc.Player().getTotalTime() + Actual = xbmc.Player().getTime() + Difference = Total - Actual + if Total > TimeFromEnd >= Difference: + time_over = True + except: + break + + if time_over: + if condition == 1: # hide server afther x second + item.show_server = False + elif condition == 2: # play next fileif exist + + # check i next file exist + current_filename = os.path.basename(item.strm_path) + base_path = os.path.basename(os.path.normpath(os.path.dirname(item.strm_path))) + path = filetools.join(config.get_videolibrary_path(), config.get_setting("folder_tvshows"),base_path) + fileList = [] + for file in os.listdir(path): + if file.endswith('.strm'): + fileList.append(file) + + fileList.sort() + + nextIndex = fileList.index(current_filename) + 1 + if nextIndex == 0 or nextIndex == len(fileList): + next_file = None + else: + next_file = fileList[nextIndex] + + # start next episode window afther x time + if next_file: + from core.item import Item + season_ep = next_file.split('.')[0] + season = season_ep.split('x')[0] + episode = season_ep.split('x')[1] + next_ep = '%sx%s' % (season, episode) + item = Item( + action= 'play_from_library', + channel= 'videolibrary', + contentEpisodeNumber= episode, + contentSeason= season, + contentTitle= next_ep, + contentType= 'tvshow', + infoLabels= {'episode': episode, 'mediatype': 'tvshow', 'season': season, 'title': next_ep}, + strm_path= filetools.join(base_path, next_file)) + + global ITEM + ITEM = item + + nextDialog = NextDialog(ND, config.get_runtime_path()) + nextDialog.show() + while platformtools.is_playing() and not nextDialog.is_still_watching(): + xbmc.sleep(100) + pass + + nextDialog.close() + logger.info('Next Episode: ' +str(nextDialog.stillwatching)) + + if nextDialog.stillwatching or nextDialog.continuewatching: + item.next_ep = True + xbmc.Player().stop() + if VL: + sleep(1) + xbmc.executebuiltin('Action(Back)') + sleep(0.5) + from platformcode.launcher import play_from_library + return play_from_library(item) + else: + item.show_server = False + if VL: + sleep(1) + xbmc.executebuiltin('Action(Back)') + sleep(0.5) + return None + + return item + + +class NextDialog(xbmcgui.WindowXMLDialog): + item = None + cancel = False + stillwatching = False + continuewatching = True + + def __init__(self, *args, **kwargs): + logger.info() + self.action_exitkeys_id = [10, 13] + self.progress_control = None + self.item = ITEM + + def set_still_watching(self, stillwatching): + self.stillwatching = stillwatching + + def set_continue_watching(self, continuewatching): + self.continuewatching = continuewatching + + def is_still_watching(self): + return self.stillwatching + + def onFocus(self, controlId): + pass + + def doAction(self): + pass + + def closeDialog(self): + self.close() + + def onClick(self, controlId): + if controlId == 3012: # Still watching + self.set_still_watching(True) + self.set_continue_watching(False) + self.close() + elif controlId == 3013: # Cancel + self.set_continue_watching(False) + self.close() + + def onAction(self, action): + logger.info() + if action == PLAYER_STOP: + self.set_continue_watching(False) + self.close() diff --git a/specials/resolverdns.py b/specials/resolverdns.py index 3f99eda2..ec244237 100644 --- a/specials/resolverdns.py +++ b/specials/resolverdns.py @@ -40,7 +40,7 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter): def __init__(self, domain, CF=False, *args, **kwargs): self.conn = sql.connect(db) self.cur = self.conn.cursor() - self.ssl_context = CustomContext(ssl.PROTOCOL_TLS, domain) + self.ssl_context = CustomContext(ssl.PROTOCOL_TLS if 'PROTOCOL_TLS' in ssl.__dict__ else ssl.PROTOCOL_SSLv3, domain) self.CF = CF # if cloudscrape is in action self.cipherSuite = kwargs.pop('cipherSuite', ssl._DEFAULT_CIPHERS) @@ -99,7 +99,7 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter): domain = parse.netloc else: raise requests.exceptions.URLRequired - self.ssl_context = CustomContext(ssl.PROTOCOL_TLS, domain) + self.ssl_context = CustomContext(ssl.PROTOCOL_TLS if 'PROTOCOL_TLS' in ssl.__dict__ else ssl.PROTOCOL_SSLv3, domain) if self.CF: self.ssl_context.options |= (ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1) self.ssl_context.set_ciphers(self.cipherSuite) diff --git a/specials/videolibrary.py b/specials/videolibrary.py index 55793688..ec50021f 100644 --- a/specials/videolibrary.py +++ b/specials/videolibrary.py @@ -558,6 +558,7 @@ def findvideos(item): server.channel = "videolibrary" server.nfo = item.nfo server.strm_path = item.strm_path + server.play_from = item.play_from #### Compatibilidad con Kodi 18: evita que se quede la ruedecedita dando vueltas en enlaces Directos if server.action == 'play': @@ -576,7 +577,10 @@ def findvideos(item): # return sorted(itemlist, key=lambda it: it.title.lower()) autoplay.play_multi_channel(item, itemlist) - + from inspect import stack + from specials import nextep + if nextep.check(item) and stack()[1][3] == 'run': + nextep.videolibrary(item) return itemlist