KoD 1.7.7
- fix di routine ai canali/server\n\n
This commit is contained in:
@@ -1,28 +1,25 @@
|
|||||||
name: Test Suite
|
name: Test Suite
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
|
||||||
- cron: '00 15 * * *'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v4.3.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.9
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
export KODI_INTERACTIVE=0
|
|
||||||
./tests/run.sh
|
./tests/run.sh
|
||||||
|
|
||||||
- name: Commit & Push changes
|
- name: Commit & Push changes
|
||||||
uses: dmnemec/copy_file_to_another_repo_action@v1.0.4
|
uses: dmnemec/copy_file_to_another_repo_action@main
|
||||||
env:
|
env:
|
||||||
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.6" provider-name="KoD Team">
|
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.7" provider-name="KoD Team">
|
||||||
<requires>
|
<requires>
|
||||||
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
|
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
|
||||||
<import addon="metadata.themoviedb.org"/>
|
<import addon="metadata.themoviedb.org"/>
|
||||||
@@ -28,9 +28,6 @@
|
|||||||
<screenshot>resources/media/screenshot-3.png</screenshot>
|
<screenshot>resources/media/screenshot-3.png</screenshot>
|
||||||
</assets>
|
</assets>
|
||||||
<news>- fix di routine ai canali/server
|
<news>- fix di routine ai canali/server
|
||||||
- disabilitati cb01anime e tantifilm
|
|
||||||
- aggiunta opzione "mostra server" nel menu contestuale della libreria
|
|
||||||
- più opzioni per quanto riguarda l'aggiornamento della videoteca
|
|
||||||
</news>
|
</news>
|
||||||
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
|
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
|
||||||
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
|
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
"eurostreaming": "https://eurostreaming.credit",
|
"eurostreaming": "https://eurostreaming.credit",
|
||||||
"eurostreaming_actor": "https://eurostreaming.care",
|
"eurostreaming_actor": "https://eurostreaming.care",
|
||||||
"filmstreaming": "https://filmstreaming.sbs",
|
"filmstreaming": "https://filmstreaming.sbs",
|
||||||
"guardaseriecam": "https://guardaserie.baby",
|
"guardaseriecam": "https://guardaserie.app",
|
||||||
"hd4me": "https://hd4me.net",
|
"hd4me": "https://hd4me.net",
|
||||||
"ilcorsaronero": "https://ilcorsaronero.link",
|
"ilcorsaronero": "https://ilcorsaronero.link",
|
||||||
"ilgeniodellostreaming_cam": "https://ilgeniodellostreaming.sbs",
|
"ilgeniodellostreaming_cam": "https://ilgeniodellostreaming.sbs",
|
||||||
|
|||||||
+1
-1
@@ -115,7 +115,7 @@ def peliculas(item):
|
|||||||
action = 'seasons'
|
action = 'seasons'
|
||||||
patron = r'<img src="(?P<thumb>[^"]+)(?:[^>]+>){4}\s*<a href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)'
|
patron = r'<img src="(?P<thumb>[^"]+)(?:[^>]+>){4}\s*<a href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)'
|
||||||
|
|
||||||
if (item.args == 'search' or item.contentType != 'movie') and inspect.stack(0)[4][3] not in ['get_channel_results']:
|
if (item.args == 'search' or item.contentType != 'movie') and not support.stackCheck(['get_channel_results']):
|
||||||
patronNext = None
|
patronNext = None
|
||||||
def itemlistHook(itemlist):
|
def itemlistHook(itemlist):
|
||||||
lastUrl = support.match(data, patron=r'href="([^"]+)">Last').match
|
lastUrl = support.match(data, patron=r'href="([^"]+)">Last').match
|
||||||
|
|||||||
+29
-10
@@ -4,14 +4,13 @@
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
import cloudscraper, json, copy, inspect
|
import cloudscraper, json, copy, inspect
|
||||||
from core import jsontools, support
|
from core import jsontools, support, httptools
|
||||||
from platformcode import autorenumber
|
from platformcode import autorenumber
|
||||||
|
|
||||||
session = cloudscraper.create_scraper()
|
# support.dbg()
|
||||||
|
|
||||||
host = support.config.get_channel_url()
|
host = support.config.get_channel_url()
|
||||||
response = session.get(host + '/archivio')
|
response = httptools.downloadpage(host + '/archivio')
|
||||||
csrf_token = support.match(response.text, patron='name="csrf-token" content="([^"]+)"').match
|
csrf_token = support.match(response.data, patron='name="csrf-token" content="([^"]+)"').match
|
||||||
headers = {'content-type': 'application/json;charset=UTF-8',
|
headers = {'content-type': 'application/json;charset=UTF-8',
|
||||||
'x-csrf-token': csrf_token,
|
'x-csrf-token': csrf_token,
|
||||||
'Cookie' : '; '.join([x.name + '=' + x.value for x in response.cookies])}
|
'Cookie' : '; '.join([x.name + '=' + x.value for x in response.cookies])}
|
||||||
@@ -153,7 +152,7 @@ def peliculas(item):
|
|||||||
item.args['order'] = order_list[order]
|
item.args['order'] = order_list[order]
|
||||||
|
|
||||||
payload = json.dumps(item.args)
|
payload = json.dumps(item.args)
|
||||||
records = session.post(host + '/archivio/get-animes', headers=headers, data=payload).json()['records']
|
records = httptools.downloadpage(host + '/archivio/get-animes', headers=headers, post=payload).json['records']
|
||||||
for it in records:
|
for it in records:
|
||||||
if not it['title']:
|
if not it['title']:
|
||||||
it['title'] = ''
|
it['title'] = ''
|
||||||
@@ -225,8 +224,28 @@ def episodios(item):
|
|||||||
|
|
||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
itemlist = [item.clone(title='StreamingCommunityWS', server='streamingcommunityws', url=str(item.scws_id))]
|
if item.scws_id:
|
||||||
# itemlist = [item.clone(title='StreamingCommunityWS', server='streamingcommunityws', url=str(item.scws_id)),
|
from time import time
|
||||||
# item.clone(title=support.config.get_localized_string(30137), server='directo', url=item.video_url)]
|
from base64 import b64encode
|
||||||
return support.server(item, itemlist=itemlist, referer=False)
|
from hashlib import md5
|
||||||
|
|
||||||
|
client_ip = support.httptools.downloadpage('http://ip-api.com/json/').json.get('query')
|
||||||
|
|
||||||
|
expires = int(time() + 172800)
|
||||||
|
token = b64encode(md5('{}{} Yc8U6r8KjAKAepEA'.format(expires, client_ip).encode('utf-8')).digest()).decode('utf-8').replace('=', '').replace('+', '-').replace('/', '_')
|
||||||
|
|
||||||
|
url = 'https://scws.work/master/{}?token={}&expires={}&n=1'.format(item.scws_id, token, expires)
|
||||||
|
|
||||||
|
itemlist = [item.clone(title=support.config.get_localized_string(30137), url=url, server='directo', action='play')]
|
||||||
|
|
||||||
|
return support.server(item, itemlist=itemlist)
|
||||||
|
|
||||||
|
|
||||||
|
def play(item):
|
||||||
|
urls = list()
|
||||||
|
info = support.match(item.url, patron=r'(http.*?rendition=(\d+)[^\s]+)').matches
|
||||||
|
|
||||||
|
if info:
|
||||||
|
for url, res in info:
|
||||||
|
urls.append(['hls [{}]'.format(res), url])
|
||||||
|
return urls
|
||||||
@@ -7,6 +7,15 @@
|
|||||||
"banner": "animeworld.png",
|
"banner": "animeworld.png",
|
||||||
"categories": ["anime", "vos"],
|
"categories": ["anime", "vos"],
|
||||||
"settings": [
|
"settings": [
|
||||||
|
{
|
||||||
|
"id": "lang",
|
||||||
|
"type": "list",
|
||||||
|
"label": "Lingua di Ricerca",
|
||||||
|
"default": 0,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true,
|
||||||
|
"lvalues": [ "Tutte", "Ita", "Sub-Ita"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "order",
|
"id": "order",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
|
|||||||
+17
-12
@@ -4,7 +4,7 @@
|
|||||||
# thanks to fatshotty
|
# thanks to fatshotty
|
||||||
# ----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
from core import httptools, support, jsontools
|
from core import httptools, support, config, jsontools
|
||||||
|
|
||||||
host = support.config.get_channel_url()
|
host = support.config.get_channel_url()
|
||||||
__channel__ = 'animeworld'
|
__channel__ = 'animeworld'
|
||||||
@@ -23,7 +23,7 @@ def get_data(item):
|
|||||||
# support.dbg()
|
# support.dbg()
|
||||||
url = httptools.downloadpage(item.url, headers=headers, follow_redirects=True, only_headers=True).url
|
url = httptools.downloadpage(item.url, headers=headers, follow_redirects=True, only_headers=True).url
|
||||||
data = support.match(url, headers=headers, follow_redirects=True).data
|
data = support.match(url, headers=headers, follow_redirects=True).data
|
||||||
if 'AWCookieVerify' in data:
|
if 'SecurityAW' in data:
|
||||||
get_cookie(data)
|
get_cookie(data)
|
||||||
data = get_data(item)
|
data = get_data(item)
|
||||||
return data
|
return data
|
||||||
@@ -37,8 +37,8 @@ def order():
|
|||||||
@support.menu
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
anime=['/filter?sort=',
|
anime=['/filter?sort=',
|
||||||
('ITA',['/filter?dub=1&sort=', 'menu', '1']),
|
('ITA',['/filter?dub=1&sort=', 'menu', 'dub=1']),
|
||||||
('SUB-ITA',['/filter?dub=0&sort=', 'menu', '0']),
|
('SUB-ITA',['/filter?dub=0&sort=', 'menu', 'dub=0']),
|
||||||
('In Corso', ['/ongoing', 'peliculas','noorder']),
|
('In Corso', ['/ongoing', 'peliculas','noorder']),
|
||||||
('Ultimi Episodi', ['/updated', 'peliculas', 'updated']),
|
('Ultimi Episodi', ['/updated', 'peliculas', 'updated']),
|
||||||
('Nuove Aggiunte',['/newest', 'peliculas','noorder' ]),
|
('Nuove Aggiunte',['/newest', 'peliculas','noorder' ]),
|
||||||
@@ -50,6 +50,7 @@ def mainlist(item):
|
|||||||
def genres(item):
|
def genres(item):
|
||||||
action = 'peliculas'
|
action = 'peliculas'
|
||||||
data = get_data(item)
|
data = get_data(item)
|
||||||
|
|
||||||
patronBlock = r'dropdown[^>]*>\s*Generi\s*<span.[^>]+>(?P<block>.*?)</ul>'
|
patronBlock = r'dropdown[^>]*>\s*Generi\s*<span.[^>]+>(?P<block>.*?)</ul>'
|
||||||
patronMenu = r'<input.*?name="(?P<name>[^"]+)" value="(?P<value>[^"]+)"\s*>[^>]+>(?P<title>[^<]+)</label>'
|
patronMenu = r'<input.*?name="(?P<name>[^"]+)" value="(?P<value>[^"]+)"\s*>[^>]+>(?P<title>[^<]+)</label>'
|
||||||
|
|
||||||
@@ -75,9 +76,10 @@ def menu(item):
|
|||||||
def submenu(item):
|
def submenu(item):
|
||||||
action = 'peliculas'
|
action = 'peliculas'
|
||||||
data = item.other
|
data = item.other
|
||||||
|
# debug=True
|
||||||
patronMenu = r'<input.*?name="(?P<name>[^"]+)" value="(?P<value>[^"]+)"\s*>[^>]+>(?P<title>[^<]+)<\/label>'
|
patronMenu = r'<input.*?name="(?P<name>[^"]+)" value="(?P<value>[^"]+)"\s*>[^>]+>(?P<title>[^<]+)<\/label>'
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
item.url = host + '/filter?' + item.name + '=' + item.value + '&dub=' + item.args + ('&sort=' if item.name != 'sort' else '')
|
item.url = '{}/filter?{}={}&{}{}'.format(host, item.name, item.value, item.args, ('&sort=' if item.name != 'sort' else ''))
|
||||||
return item
|
return item
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
@@ -85,9 +87,10 @@ def submenu(item):
|
|||||||
def newest(categoria):
|
def newest(categoria):
|
||||||
support.info(categoria)
|
support.info(categoria)
|
||||||
item = support.Item()
|
item = support.Item()
|
||||||
|
lang = config.get_setting('lang', channel=item.channel)
|
||||||
try:
|
try:
|
||||||
if categoria == "anime":
|
if categoria == "anime":
|
||||||
item.url = host + '/updated'
|
item.url = host
|
||||||
item.args = "updated"
|
item.args = "updated"
|
||||||
return peliculas(item)
|
return peliculas(item)
|
||||||
# Continua la ricerca in caso di errore
|
# Continua la ricerca in caso di errore
|
||||||
@@ -98,13 +101,13 @@ def newest(categoria):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def search(item, texto):
|
def search(item, text):
|
||||||
support.info(texto)
|
support.info(text)
|
||||||
if item.search:
|
if item.search:
|
||||||
item.url = host + '/filter?dub=' + item.args + '&keyword=' + texto + '&sort='
|
item.url = '{}/filter?{}&keyword={}&sort='.format(host, item.args, text)
|
||||||
else:
|
else:
|
||||||
item.args = 'noorder'
|
lang = ['?', '?dub=1&', '?dub=0&'][config.get_setting('lang', channel=item.channel)]
|
||||||
item.url = host + '/search?keyword=' + texto
|
item.url = '{}/filter{}&keyword={}&sort='.format(host, lang, text)
|
||||||
item.contentType = 'tvshow'
|
item.contentType = 'tvshow'
|
||||||
try:
|
try:
|
||||||
return peliculas(item)
|
return peliculas(item)
|
||||||
@@ -118,8 +121,8 @@ def search(item, texto):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
data = get_data(item)
|
||||||
anime = True
|
anime = True
|
||||||
# debug = True
|
|
||||||
if item.args not in ['noorder', 'updated'] and not item.url[-1].isdigit(): item.url += order() # usa l'ordinamento di configura canale
|
if item.args not in ['noorder', 'updated'] and not item.url[-1].isdigit(): item.url += order() # usa l'ordinamento di configura canale
|
||||||
data = get_data(item)
|
data = get_data(item)
|
||||||
|
|
||||||
@@ -185,7 +188,9 @@ def findvideos(item):
|
|||||||
else:
|
else:
|
||||||
dataJson = support.match(host + '/api/episode/info?id=' + epID + '&alt=0', headers=headers).data
|
dataJson = support.match(host + '/api/episode/info?id=' + epID + '&alt=0', headers=headers).data
|
||||||
json = jsontools.load(dataJson)
|
json = jsontools.load(dataJson)
|
||||||
|
|
||||||
title = support.match(json['grabber'], patron=r'server\d+.([^.]+)', string=True).match
|
title = support.match(json['grabber'], patron=r'server\d+.([^.]+)', string=True).match
|
||||||
if title: itemlist.append(item.clone(action="play", title=title, url=json['grabber'].split('=')[-1], server='directo'))
|
if title: itemlist.append(item.clone(action="play", title=title, url=json['grabber'].split('=')[-1], server='directo'))
|
||||||
else: urls.append(json['grabber'])
|
else: urls.append(json['grabber'])
|
||||||
|
# support.info(urls)
|
||||||
return support.server(item, urls, itemlist)
|
return support.server(item, urls, itemlist)
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ def peliculas(item):
|
|||||||
@support.scrape
|
@support.scrape
|
||||||
def episodios(item):
|
def episodios(item):
|
||||||
patronBlock = r'<div class="tab-pane fade" id="season-(?P<season>.)"(?P<block>.*?)</ul>\s*</div>'
|
patronBlock = r'<div class="tab-pane fade" id="season-(?P<season>.)"(?P<block>.*?)</ul>\s*</div>'
|
||||||
patron = r'<a href="#" allowfullscreen data-link="(?P<url>[^"]+).*?title="(?P<title>[^"]+)(?P<lang>[sS][uU][bB]-?[iI][tT][aA])?\s*">(?P<episode>[^<]+)'
|
patron = r'(?P<data><a href="#" allowfullscreen data-link="[^"]+.*?title="(?P<title>[^"]+)(?P<lang>[sS][uU][bB]-?[iI][tT][aA])?\s*">(?P<episode>[^<]+).*?</li>)'
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
# debugBlock = True
|
# debug = True
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
@@ -68,4 +68,4 @@ def search(item, text):
|
|||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
return support.server(item, item.url)
|
return support.server(item, item.data)
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ def peliculas(item):
|
|||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
if not sceneTitle:
|
if not sceneTitle:
|
||||||
item.title = item.title.replace('_', ' ')
|
item.title = item.title.replace('_', ' ')
|
||||||
|
item.fulltitle = item.fulltitle.replace('_', ' ')
|
||||||
item.title = support.scrapertools.decodeHtmlentities(support.urlparse.unquote(item.title))
|
item.title = support.scrapertools.decodeHtmlentities(support.urlparse.unquote(item.title))
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
if 'search' not in item.args:
|
if 'search' not in item.args:
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ headers = [['Referer', host]]
|
|||||||
@support.menu
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
menu = [
|
menu = [
|
||||||
('Film', ['/film/', 'list', 'film']),
|
('Film', ['/film/', 'list', 'film']),
|
||||||
('Per Genere', ['', 'list', 'genere']),
|
('Per Genere', ['', 'list', 'genere']),
|
||||||
('Al Cinema', ['/cinema/', 'list', 'film']),
|
('Al Cinema', ['/cinema/', 'list', 'film']),
|
||||||
('Sottotitolati', ['/sub-ita/', 'list', 'film']),
|
('Sub-ITA', ['/sub-ita/', 'list', 'film']),
|
||||||
('Top del Mese', ['/top-del-mese.html', 'list', 'film'])
|
('Top del Mese', ['/top-del-mese.html', 'list', 'film'])
|
||||||
|
|
||||||
]
|
]
|
||||||
search = ''
|
search = ''
|
||||||
|
|
||||||
|
|||||||
+18
-27
@@ -4,7 +4,8 @@
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from core import support
|
from core import support, httptools
|
||||||
|
from platformcode import logger
|
||||||
|
|
||||||
DRM = 'com.widevine.alpha'
|
DRM = 'com.widevine.alpha'
|
||||||
key_widevine = "https://la7.prod.conax.cloud/widevine/license"
|
key_widevine = "https://la7.prod.conax.cloud/widevine/license"
|
||||||
@@ -27,7 +28,7 @@ def mainlist(item):
|
|||||||
('Replay {bold}', ['', 'replay_channels'])]
|
('Replay {bold}', ['', 'replay_channels'])]
|
||||||
|
|
||||||
menu = [('Programmi TV {bullet bold}', ['/tutti-i-programmi', 'peliculas', '', 'tvshow']),
|
menu = [('Programmi TV {bullet bold}', ['/tutti-i-programmi', 'peliculas', '', 'tvshow']),
|
||||||
('Teche La7 {bullet bold}', ['/i-protagonisti', 'peliculas', '', 'tvshow'])]
|
('Teche La7 {bullet bold}', ['/la7teche', 'peliculas', '', 'tvshow'])]
|
||||||
|
|
||||||
search = ''
|
search = ''
|
||||||
return locals()
|
return locals()
|
||||||
@@ -83,13 +84,18 @@ def search(item, text):
|
|||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
search = item.search
|
search = item.search
|
||||||
|
action = 'episodios'
|
||||||
|
pagination = 20
|
||||||
disabletmdb = True
|
disabletmdb = True
|
||||||
addVideolibrary = False
|
addVideolibrary = False
|
||||||
downloadEnabled = False
|
downloadEnabled = False
|
||||||
action = 'episodios'
|
|
||||||
patron = r'<a href="(?P<url>[^"]+)"[^>]+><div class="[^"]+" data-background-image="(?P<t>[^"]+)"></div><div class="titolo">\s*(?P<title>[^<]+)<'
|
patron = r'<a href="(?P<url>[^"]+)"[^>]+><div class="[^"]+" data-background-image="(?P<t>[^"]+)"></div><div class="titolo">\s*(?P<title>[^<]+)<'
|
||||||
|
|
||||||
|
if 'la7teche' in item.url:
|
||||||
|
patron = r'<a href="(?P<url>[^"]+)" title="(?P<title>[^"]+)" class="teche-i-img".*?url\(\'(?P<thumb>[^\']+)'
|
||||||
|
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
item.thumbnail = 'http:' + item.t if item.t.startswith('//') else item.t if item.t else item.thumbnail
|
|
||||||
item.fanart = item.thumb
|
item.fanart = item.thumb
|
||||||
return item
|
return item
|
||||||
return locals()
|
return locals()
|
||||||
@@ -97,33 +103,18 @@ def peliculas(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def episodios(item):
|
def episodios(item):
|
||||||
data = support.match(item).data
|
|
||||||
# debug = True
|
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
if '>puntate<' in data:
|
|
||||||
patronBlock = r'>puntate<(?P<block>.*?)home-block-outbrain'
|
|
||||||
url = support.match(data, patron=r'>puntate<[^>]+>[^>]+>[^>]+><a href="([^"]+)"').match
|
|
||||||
data += support.match(host + url).data
|
|
||||||
else:
|
|
||||||
item.url += '/video'
|
|
||||||
data = support.match(item).data
|
|
||||||
|
|
||||||
patron = r'(?:<a href="(?P<url>[^"]+)">[^>]+><div class="[^"]+" data-background-image="(?P<t>[^"]*)">[^>]+>[^>]+>[^>]+>(?:[^>]+>)?(?:[^>]+>){6}?)\s*(?P<title>[^<]+)<(?:[^>]+>[^>]+>[^>]+><div class="data">(?P<date>[^<]+))?|class="heading">[^>]+>(?P<Title>[^<]+).*?window.shareUrl = "(?P<Url>[^"]+)".*?poster:\s*"(?P<Thumb>[^"]+)", title: "(?P<desc>[^"]+)"'
|
|
||||||
patronNext = r'<a href="([^"]+)">›'
|
|
||||||
addVideolibrary = False
|
addVideolibrary = False
|
||||||
downloadEnabled = False
|
downloadEnabled = False
|
||||||
|
|
||||||
def itemHook(item):
|
if 'la7teche' in item.url:
|
||||||
if item.Thumb: item.t = item.Thumb
|
patron = r'<a href="(?P<url>[^"]+)">\s*<div class="holder-bg">.*?data-background-image="(?P<thumb>[^"]+)(?:[^>]+>){4}\s*(?P<title>[^<]+)(?:(?:[^>]+>){2}\s*(?P<plot>[^<]+))?'
|
||||||
item.thumbnail = 'http:' + item.t if item.t.startswith('//') else item.t if item.t else item.thumbnail
|
else:
|
||||||
if item.Title: item.title = support.typo(item.Title, 'bold')
|
data = str(support.match(item.url, patron=r'"home-block home-block--oggi(.*?)</section>').matches)
|
||||||
if item.date:
|
data += httptools.downloadpage(item.url + '/video').data
|
||||||
item.title = support.re.sub(r'[Pp]untata (?:del )?\d+/\d+/\d+', '', item.title)
|
|
||||||
item.title += support.typo(item.date, '_ [] bold')
|
patron = r'item[^>]+>\s*<a href="(?P<url>[^"]+)">.*?image="(?P<thumb>[^"]+)(?:[^>]+>){4,5}\s*(?P<title>[\d\w][^<]+)(?:(?:[^>]+>){7}\s*(?P<title2>[\d\w][^<]+))?'
|
||||||
if item.desc: item.plot = item.desc
|
patronNext = r'<a href="([^"]+)">›'
|
||||||
item.forcethumb = True
|
|
||||||
item.fanart = item.thumbnail
|
|
||||||
return item
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+17
-20
@@ -9,37 +9,33 @@ host = 'https://metalvideo.com'
|
|||||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||||
|
|
||||||
|
|
||||||
@support.scrape
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
|
menu = [('Generi',['', 'genres']),
|
||||||
|
('Ultimi Video',['/videos/latest', 'peliculas']),
|
||||||
|
('Top Video',['/videos/top', 'peliculas']),
|
||||||
|
('Cerca...',['','search',])]
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
|
@support.scrape
|
||||||
|
def genres(item):
|
||||||
item.url = host
|
item.url = host
|
||||||
action = 'peliculas'
|
action = 'peliculas'
|
||||||
patronBlock = r'<ul class="dropdown-menu(?P<block>.*?)</ul>\s*</div'
|
patronBlock = r'<div class="swiper-slide">(?P<block>.*?)<button'
|
||||||
patron = r'<a href="(?P<url>[^"]+)"(?: class="")?>(?P<title>[^<]+)<'
|
patron = r'class="" href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)<'
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
item.thumbnail = support.thumb('music')
|
item.thumbnail = support.thumb('music')
|
||||||
item.contentType = 'music'
|
item.contentType = 'music'
|
||||||
return item
|
return item
|
||||||
def itemlistHook(itemlist):
|
|
||||||
itemlist.pop(0)
|
|
||||||
itemlist.append(
|
|
||||||
support.Item(
|
|
||||||
channel=item.channel,
|
|
||||||
title=support.typo('Cerca...', 'bold'),
|
|
||||||
contentType='music',
|
|
||||||
url=item.url,
|
|
||||||
action='search',
|
|
||||||
thumbnail=support.thumb('search')))
|
|
||||||
|
|
||||||
support.channel_config(item, itemlist)
|
|
||||||
return itemlist
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
# debug=True
|
# debug=True
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
patron= r'<img src="[^"]+" alt="(?P<title>[^"]+)" data-echo="(?P<thumb>[^"]+)"(?:[^>]+>){7}<a href="(?P<url>[^"]+)"'
|
patron= r'<a href="(?P<url>[^"]+)"[^>]+>\s*<img src="(?P<thumb>[^"]+)" alt="(?P<title>[^"]+)"[^>]*>'
|
||||||
patronNext = r'<a href="([^"]+)">(?:»|»)'
|
patronNext = r'<a href="([^"]+)" data-load="[^"]+" class="[^"]+" title="Next'
|
||||||
typeContentDict = {'': 'music'}
|
typeContentDict = {'': 'music'}
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
item.contentType = 'music'
|
item.contentType = 'music'
|
||||||
@@ -49,12 +45,13 @@ def peliculas(item):
|
|||||||
|
|
||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
return support.server(item, Videolibrary=False)
|
data = support.match(item, patron=r'<source src="[^"]+').match
|
||||||
|
return support.server(item, Videolibrary=False, data=data)
|
||||||
|
|
||||||
|
|
||||||
def search(item, text):
|
def search(item, text):
|
||||||
support.info(text)
|
support.info(text)
|
||||||
item.url = host + '/search.php?keywords=' + text + '&video-id='
|
item.url = host + '/search?keyword=' + text
|
||||||
try:
|
try:
|
||||||
return peliculas(item)
|
return peliculas(item)
|
||||||
# Continua la ricerca in caso di errore
|
# Continua la ricerca in caso di errore
|
||||||
|
|||||||
+13
-12
@@ -22,16 +22,17 @@ def mainlist(item):
|
|||||||
top = [('Dirette {bold}', ['/dirette', 'live', '/palinsesto/onAir.json']),
|
top = [('Dirette {bold}', ['/dirette', 'live', '/palinsesto/onAir.json']),
|
||||||
('Replay {bold}', ['/guidatv', 'replayMenu', '/guidatv.json'])]
|
('Replay {bold}', ['/guidatv', 'replayMenu', '/guidatv.json'])]
|
||||||
|
|
||||||
menu = [('Film {bullet bold}', ['/film', 'menu', '/tipologia/film/index.json']),
|
menu = [('Film {bold}', ['/film', 'menu', '/tipologia/film/index.json']),
|
||||||
('Serie italiane {bullet bold}', ['/serietv', 'menu', '/tipologia/serieitaliane/index.json']),
|
('Serie italiane {bold}', ['/serieitaliane', 'menu', '/tipologia/serieitaliane/index.json']),
|
||||||
# ('Fiction {bullet bold}', ['/fiction', 'menu', '/tipologia/fiction/index.json']),
|
('Serie Internazionali {bold}', ['/serieinternazionali', 'menu', '/tipologia/serieinternazionali/index.json']),
|
||||||
('Documentari {bullet bold}', ['/documentari', 'menu', '/tipologia/documentari/index.json']),
|
('Programmi TV{bold}', ['/programmi', 'menu', '/tipologia/programmi/index.json']),
|
||||||
('Programmi TV{bullet bold}', ['/programmi', 'menu', '/tipologia/programmi/index.json']),
|
('Documentari {bold}', ['/documentari', 'menu', '/tipologia/documentari/index.json']),
|
||||||
('Programmi per Bambini {bullet bold}', ['/bambini', 'menu', '/tipologia/bambini/index.json']),
|
('Bambini {bold}', ['/bambini', 'menu', '/tipologia/bambini/index.json']),
|
||||||
('Teen {bullet bold}', ['/teen', 'menu', '/tipologia/teen/index.json']),
|
('Teen {bold}', ['/teen', 'menu', '/tipologia/teen/index.json']),
|
||||||
('Learning {bullet bold}', ['/learning', 'menu', '/tipologia/learning/index.json']),
|
('Musica e Teatro {bold}', ['/musica-e-teatro', 'menu', '/tipologia/musica-e-teatro/index.json']),
|
||||||
('Teche Rai {bullet bold storia}', ['/techerai', 'menu', '/tipologia/techerai/index.json']),
|
('Teche Rai {bold storia}', ['/techerai', 'menu', '/tipologia/techerai/index.json']),
|
||||||
('Musica e Teatro {bullet bold}', ['/musica-e-teatro', 'menu', '/tipologia/musica-e-teatro/index.json'])
|
('Learning {bold}', ['/learning', 'menu', '/tipologia/learning/index.json']),
|
||||||
|
('Rai Italy{bold tv}', ['/raiitaly', 'menu', '/tipologia/raiitaly/index.json'])
|
||||||
]
|
]
|
||||||
|
|
||||||
search = ''
|
search = ''
|
||||||
@@ -41,6 +42,7 @@ def mainlist(item):
|
|||||||
|
|
||||||
def menu(item):
|
def menu(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
|
|
||||||
itemlist = []
|
itemlist = []
|
||||||
item.disable_videolibrary = True
|
item.disable_videolibrary = True
|
||||||
action = 'peliculas'
|
action = 'peliculas'
|
||||||
@@ -60,7 +62,6 @@ def menu(item):
|
|||||||
action = 'menu'
|
action = 'menu'
|
||||||
thumb = support.thumb('genres')
|
thumb = support.thumb('genres')
|
||||||
itemlist.append(item.clone(title=support.typo(it['name'], 'bold'), data=it.get('contents', item.data), thumbnail=thumb, action=action))
|
itemlist.append(item.clone(title=support.typo(it['name'], 'bold'), data=it.get('contents', item.data), thumbnail=thumb, action=action))
|
||||||
|
|
||||||
return itemlist
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
@@ -256,7 +257,7 @@ def getUrl(url):
|
|||||||
elif url.startswith("/"): url = host + url
|
elif url.startswith("/"): url = host + url
|
||||||
|
|
||||||
url = url.replace(".html?json", ".json").replace("/?json",".json").replace("?json",".json").replace(" ", "%20")
|
url = url.replace(".html?json", ".json").replace("/?json",".json").replace("?json",".json").replace(" ", "%20")
|
||||||
|
logger.debug('URL', url)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ def episodios(item):
|
|||||||
action='findvideos',
|
action='findvideos',
|
||||||
contentType='episode',
|
contentType='episode',
|
||||||
contentSerieName=item.fulltitle,
|
contentSerieName=item.fulltitle,
|
||||||
url='{}/watch/{}?e={}'.format(host, se['title_id'], ep['id'])))
|
url='{}/iframe/{}?episode_id={}'.format(host, se['title_id'], ep['id'])))
|
||||||
|
|
||||||
if config.get_setting('episode_info') and not support.stackCheck(['add_tvshow', 'get_newest']):
|
if config.get_setting('episode_info') and not support.stackCheck(['add_tvshow', 'get_newest']):
|
||||||
support.tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
support.tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
||||||
@@ -235,10 +235,8 @@ def episodios(item):
|
|||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
support.callAds('https://thaudray.com/5/3523301', host)
|
support.callAds('https://thaudray.com/5/3523301', host)
|
||||||
# Fix for old items in videolibrary
|
|
||||||
if item.episodeid and item.episodeid not in item.url:
|
|
||||||
item.url += item.episodeid
|
|
||||||
|
|
||||||
itemlist = [item.clone(title=channeltools.get_channel_parameters(item.channel)['title'], url=item.url, server='streamingcommunityws')]
|
itemlist = [item.clone(title=channeltools.get_channel_parameters(item.channel)['title'],
|
||||||
|
url=item.url.replace('/watch/', '/iframe/'), server='streamingcommunityws')]
|
||||||
return support.server(item, itemlist=itemlist, referer=False)
|
return support.server(item, itemlist=itemlist, referer=False)
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
# Canale per Tantifilm
|
# Canale per Tantifilm
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
from core import scrapertools, httptools, support
|
from core import support
|
||||||
from core.item import Item
|
from core.item import Item
|
||||||
from core.support import info
|
|
||||||
from platformcode import logger
|
from platformcode import logger
|
||||||
from platformcode import config
|
from platformcode import config
|
||||||
|
|
||||||
|
|||||||
+14
-47
@@ -13,12 +13,10 @@ headers = [['Referer', host]]
|
|||||||
@support.menu
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
|
|
||||||
# top = [('Novità',['', 'peliculas', 'new', 'tvshow']),
|
anime =['/category/anime',
|
||||||
# ('Aggiornamenti', ['', 'peliculas', 'last', 'tvshow'])]
|
('ITA',['/lista-anime-ita','peliculas',]),
|
||||||
# tvshow = ['/category/serie-tv/']
|
('Sub-ITA',['/lista-anime-sub-ita', 'peliculas'])]
|
||||||
anime =['/category/anime/']
|
# ('Film Animati',['/lista-anime-ita','peliculas', '', 'movie'])]
|
||||||
# ('Sub-Ita',['/category/anime-sub-ita/', 'peliculas', 'sub']),
|
|
||||||
# ('Film Animati',['/category/film-animazione/','peliculas', '', 'movie'])]
|
|
||||||
search = ''
|
search = ''
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
@@ -39,52 +37,28 @@ def search(item, text):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def newest(categoria):
|
|
||||||
support.info(categoria)
|
|
||||||
item = support.Item()
|
|
||||||
try:
|
|
||||||
item.contentType = 'undefined'
|
|
||||||
item.url= host
|
|
||||||
item.args= 'new'
|
|
||||||
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
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
# debugBlock = True
|
anime = True
|
||||||
# debug = True
|
|
||||||
# search = item.text
|
|
||||||
if item.contentType != 'movie': anime = True
|
|
||||||
action = 'check'
|
action = 'check'
|
||||||
blacklist = ['-Film Animazione disponibili in attesa di recensione ']
|
|
||||||
|
|
||||||
if item.action == 'search':
|
deflang = 'ITA' if 'sub' not in item.url else 'Sub-ITA'
|
||||||
pagination = ''
|
if 'lista' in item.url:
|
||||||
#patronBlock = '"lcp_catlist"[^>]+>(?P<block>.*)</ul>'
|
pagination = 20
|
||||||
patronBlock = '<main[^>]+>(?P<block>.*?)</ma'
|
patron = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<]+)'
|
||||||
#patron = r'href="(?P<url>[^"]+)" title="(?P<title>[^"]+)"'
|
|
||||||
patron = r'<a href="(?P<url>[^"]+)"[^>]*>(?P<title>[^<]+)<[^>]+>[^>]+>\s*<div'
|
|
||||||
elif item.args == 'last':
|
|
||||||
patronBlock = '(?:Aggiornamenti|Update)</h2>(?P<block>.*?)</ul>'
|
|
||||||
patron = r'<a href="(?P<url>[^"]+)">\s*<img[^>]+src[set]{0,3}="(?P<thumbnail>[^ ]+)[^>]+>\s*<span[^>]+>(?P<title>[^<]+)'
|
|
||||||
else:
|
else:
|
||||||
patronBlock = '<main[^>]+>(?P<block>.*)</main>'
|
patronBlock = '<main[^>]+>(?P<block>.*)</main>'
|
||||||
# patron = r'<a href="(?P<url>[^"]+)" rel="bookmark">(?P<title>[^<]+)</a>[^>]+>[^>]+>[^>]+><img.*?src="(?P<thumb>[^"]+)".*?<p>(?P<plot>[^<]+)</p>.*?<span class="cat-links">Pubblicato in.*?.*?(?P<type>(?:[Ff]ilm|</artic))[^>]+>'
|
patron = r'(?i)<a href="(?P<url>[^"]+)" rel="bookmark">(?P<title>[^<]+)</a>(:?[^>]+>){3}(?:<img.*?src="(?P<thumb>[^"]+)")?.*?<p>(?P<plot>[^<]+)</p>.*?tag">.*?(?P<type>(?:film|serie|anime))(?P<cat>.*?)</span>'
|
||||||
patron = r'<a href="(?P<url>[^"]+)" rel="bookmark">(?P<title>[^<]+)</a>(:?[^>]+>){3}(?:<img.*?src="(?P<thumb>[^"]+)")?.*?<p>(?P<plot>[^<]+)</p>.*?tag">.*?(?P<type>(?:[Ff]ilm|</art|Serie Tv))'
|
|
||||||
typeContentDict={'movie':['film']}
|
typeContentDict={'movie':['film']}
|
||||||
typeActionDict={'findvideos':['film']}
|
typeActionDict={'findvideos':['film']}
|
||||||
patronNext = '<a class="next page-numbers" href="([^"]+)">'
|
patronNext = '<a class="next page-numbers" href="([^"]+)">'
|
||||||
|
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
support.info(item.title)
|
support.info(item.title)
|
||||||
if item.args == 'sub':
|
if 'sub/ita' in item.cat.lower():
|
||||||
item.title += support.typo('Sub-ITA', 'bold color kod _ []')
|
item.title = item.title.replace('[ITA]', '[Sub-ITA]')
|
||||||
item.contentLanguage = 'Sub-ITA'
|
item.contentLanguage = 'Sub-ITA'
|
||||||
return item
|
return item
|
||||||
return locals()
|
return locals()
|
||||||
@@ -101,18 +75,11 @@ def check(item):
|
|||||||
def episodios(item):
|
def episodios(item):
|
||||||
anime = True
|
anime = True
|
||||||
patron = r'>\s*(?:(?P<season>\d+)(?:×|x|×))?(?P<episode>\d+)(?:\s+–\s+)?[ –]+(?P<title2>[^<]+)[ –]+<a (?P<data>.*?)(?:<br|</p)'
|
patron = r'>\s*(?:(?P<season>\d+)(?:×|x|×))?(?P<episode>\d+)(?:\s+–\s+)?[ –]+(?P<title2>[^<]+)[ –]+<a (?P<data>.*?)(?:<br|</p)'
|
||||||
|
|
||||||
# if inspect.stack(0)[1][3] not in ['find_episodes']:
|
|
||||||
# from platformcode import autorenumber
|
|
||||||
# autorenumber.start(itemlist, item)
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
servers = support.server(item, data=item.data)
|
return support.server(item, data=item.data)
|
||||||
return servers
|
|
||||||
|
|
||||||
# return support.server(item, item.data if item.contentType != 'movie' else support.match(item.url, headers=headers).data )
|
|
||||||
|
|
||||||
|
|
||||||
def clean_title(title):
|
def clean_title(title):
|
||||||
|
|||||||
+117
-73
@@ -3,93 +3,137 @@
|
|||||||
# Canale per tunein
|
# Canale per tunein
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
from core import scrapertools, support
|
from core import httptools, support
|
||||||
from platformcode import logger
|
from platformcode import logger
|
||||||
|
|
||||||
|
|
||||||
host = 'http://api.radiotime.com'
|
host = 'http://api.radiotime.com'
|
||||||
headers = [['Referer', host]]
|
args = 'formats=mp3,aac,ogg,flash,html,hls,wma&partnerId=RadioTime&itemToken='
|
||||||
|
|
||||||
|
|
||||||
@support.scrape
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
item.url = host
|
menu = [('Musica {bullet music}' ,['/categories/music?{}'.format(args), 'radio', '', 'music']),
|
||||||
action = 'radio'
|
('Sport {bullet music}' ,['/categories/sports?{}'.format(args), 'radio', '', 'music']),
|
||||||
patron = r'text="(?P<title>[^"]+)" URL="(?P<url>[^"]+)"'
|
('Notizie e Dibattiti {bullet music}' ,['/categories/c57922?{}'.format(args), 'radio', '' 'music']),
|
||||||
def itemHook(item):
|
('Podcast {bullet music}' ,['/categories/c100000088?{}'.format(args), 'radio', '', 'music']),
|
||||||
item.thumbnail = support.thumb('music')
|
('Audiolibri {bullet music}' ,['/categories/c100006408?{}'.format(args), 'radio', '', 'music']),
|
||||||
item.contentType = 'music'
|
('Luogo {bullet music}' ,['/categories/regions?{}'.format(args), 'radio', '', 'music']),
|
||||||
return item
|
('Lingua {bullet music}' ,['/categories/languages?{}'.format(args), 'radio', '', 'music'])]
|
||||||
def itemlistHook(itemlist):
|
search =''
|
||||||
itemlist.append(
|
|
||||||
item.clone(title=support.typo('Cerca...', 'bold color kod'), action='search', thumbnail=support.thumb('search')))
|
|
||||||
support.channel_config(item, itemlist)
|
|
||||||
return itemlist
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
def radio(item):
|
|
||||||
support.info()
|
|
||||||
itemlist = []
|
|
||||||
data = support.match(item, patron= r'text="(?P<title>[^\("]+)(?:\((?P<location>[^\)]+)\))?" URL="(?P<url>[^"]+)" bitrate="(?P<quality>[^"]+)" reliability="[^"]+" guide_id="[^"]+" subtext="(?P<song>[^"]+)" genre_id="[^"]+" formats="(?P<type>[^"]+)" (?:playing="[^"]+" )?(?:playing_image="[^"]+" )?(?:show_id="[^"]+" )?(?:item="[^"]+" )?image="(?P<thumb>[^"]+)"')
|
|
||||||
if data.matches:
|
|
||||||
for title, location, url, quality, song, type, thumbnail in data.matches:
|
|
||||||
title = scrapertools.decodeHtmlentities(title)
|
|
||||||
itemlist.append(
|
|
||||||
item.clone(title = support.typo(title, 'bold') + support.typo(quality + ' kbps','_ [] bold color kod'),
|
|
||||||
thumbnail = thumbnail,
|
|
||||||
url = url,
|
|
||||||
contentType = 'music',
|
|
||||||
plot = support.typo(location, 'bold') + '\n' + song,
|
|
||||||
action = 'findvideos'))
|
|
||||||
else:
|
|
||||||
matches = support.match(data.data, patron= r'text="(?P<title>[^\("]+)(?:\([^\)]+\))?" URL="(?P<url>[^"]+)" (?:guide_id="[^"]+" )?(?:stream_type="[^"]+" )?topic_duration="(?P<duration>[^"]+)" subtext="(?P<plot>[^"]+)" item="[^"]+" image="(?P<thumb>[^"]+)"').matches
|
|
||||||
if matches:
|
|
||||||
for title, url, duration, plot, thumbnail in matches:
|
|
||||||
title = scrapertools.unescape(title)
|
|
||||||
infoLabels={}
|
|
||||||
infoLabels['duration'] = duration
|
|
||||||
itemlist.append(
|
|
||||||
item.clone(title = support.typo(title, 'bold'),
|
|
||||||
thumbnail = thumbnail,
|
|
||||||
infolLbels = infoLabels,
|
|
||||||
url = url,
|
|
||||||
contentType = 'music',
|
|
||||||
plot = plot,
|
|
||||||
action = 'findvideos'))
|
|
||||||
else:
|
|
||||||
matches = support.match(data.data, patron= r'text="(?P<title>[^"]+)" URL="(?P<url>[^"]+)"').matches
|
|
||||||
for title, url in matches:
|
|
||||||
title = scrapertools.unescape(title)
|
|
||||||
itemlist.append(
|
|
||||||
item.clone(channel = item.channel,
|
|
||||||
title = support.typo(title, 'bold'),
|
|
||||||
thumbnail = item.thumbnail,
|
|
||||||
url = url,
|
|
||||||
action = 'radio'))
|
|
||||||
support.nextPage(itemlist, item, data.data, r'(?P<url>[^"]+)" key="nextStations')
|
|
||||||
return itemlist
|
|
||||||
|
|
||||||
|
|
||||||
def findvideos(item):
|
|
||||||
import xbmc
|
|
||||||
itemlist = []
|
|
||||||
item.action = 'play'
|
|
||||||
urls = support.match(item.url).data.strip().split()
|
|
||||||
for url in urls:
|
|
||||||
item.url= url
|
|
||||||
item.server = 'directo'
|
|
||||||
itemlist.append(item)
|
|
||||||
return itemlist
|
|
||||||
|
|
||||||
|
|
||||||
def search(item, text):
|
def search(item, text):
|
||||||
support.info(text)
|
support.info(text)
|
||||||
item.url = host + '/Search.ashx?query=' +text
|
itemlist = list()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return radio(item)
|
js = httptools.downloadpage('{}/profiles?fullTextSearch=true&query={}&{}'.format(host, text, args)).json
|
||||||
|
data = js.get('Items', {})
|
||||||
|
for c in data:
|
||||||
|
if c.get('Pivots',{}).get('More',{}).get('Url', ''):
|
||||||
|
data = httptools.downloadpage(c.get('Pivots',{}).get('More',{}).get('Url', '')).json.get('Items',{})
|
||||||
|
else:
|
||||||
|
data = c.get('Children')
|
||||||
|
if data:
|
||||||
|
itemlist.extend(buildItemList(item, data))
|
||||||
|
|
||||||
|
if js.get('Paging', {}).get('Next'):
|
||||||
|
support.nextPage(itemlist, item, next_page=js.get('Paging', {}).get('Next'))
|
||||||
|
return itemlist
|
||||||
# Continua la ricerca in caso di errore
|
# Continua la ricerca in caso di errore
|
||||||
except:
|
except:
|
||||||
import sys
|
import sys
|
||||||
for line in sys.exc_info():
|
for line in sys.exc_info():
|
||||||
logger.error("%s" % line)
|
logger.error(line)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def radio(item):
|
||||||
|
itemlist = list()
|
||||||
|
js = dict()
|
||||||
|
if item.data:
|
||||||
|
data = item.data
|
||||||
|
else:
|
||||||
|
js = httptools.downloadpage(item.url).json
|
||||||
|
data = js.get('Items', {})
|
||||||
|
|
||||||
|
itemlist = buildItemList(item, data)
|
||||||
|
if js.get('Paging', {}).get('Next'):
|
||||||
|
support.nextPage(itemlist, item, next_page=js.get('Paging', {}).get('Next'))
|
||||||
|
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def buildItemList(item, data):
|
||||||
|
itemlist = list()
|
||||||
|
# support.dbg()
|
||||||
|
for c in data:
|
||||||
|
item.data = ''
|
||||||
|
item.action = 'radio'
|
||||||
|
token = c.get('Context',{}).get('Token','')
|
||||||
|
if not token:
|
||||||
|
token = c.get('Actions', {}).get('Context',{}).get('Token','')
|
||||||
|
if not c.get('Title', c.get('AccessibilityTitle')) or 'premium' in c.get('Title', c.get('AccessibilityTitle')).lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if c.get('Children'):
|
||||||
|
if len(data) > 1:
|
||||||
|
if c.get('Pivots',{}).get('More',{}).get('Url', ''):
|
||||||
|
itm = item.clone(title=c.get('Title', c.get('AccessibilityTitle')),
|
||||||
|
url=c.get('Pivots',{}).get('More',{}).get('Url', ''),
|
||||||
|
token=token)
|
||||||
|
else:
|
||||||
|
itm = item.clone(title=c.get('Title', c.get('AccessibilityTitle')),
|
||||||
|
data=c.get('Children'),
|
||||||
|
token=token)
|
||||||
|
else:
|
||||||
|
if c.get('Pivots',{}).get('More',{}).get('Url', ''):
|
||||||
|
data = httptools.downloadpage(c.get('Pivots',{}).get('More',{}).get('Url', '')).json.get('Items', {})
|
||||||
|
else:
|
||||||
|
data = c.get('Children')
|
||||||
|
return buildItemList(item, data)
|
||||||
|
|
||||||
|
elif c.get('GuideId'):
|
||||||
|
title = c.get('Title', c.get('AccessibilityTitle'))
|
||||||
|
plot = '[B]{}[/B]\n{}'.format(c.get('Subtitle', ''), c.get('Description', ''))
|
||||||
|
thumbnail = c.get('Image', '')
|
||||||
|
if c.get('GuideId').startswith('s'):
|
||||||
|
itm = item.clone(title=title,
|
||||||
|
plot=plot,
|
||||||
|
thumbnail=thumbnail,
|
||||||
|
url = 'http://opml.radiotime.com/Tune.ashx?render=json&id={}&{}{}'.format(c.get('GuideId'), args, token),
|
||||||
|
action = 'findvideos')
|
||||||
|
|
||||||
|
else:
|
||||||
|
itm = item.clone(title=title,
|
||||||
|
plot=plot,
|
||||||
|
thumbnail=thumbnail,
|
||||||
|
url = c.get('Actions', {}).get('Browse',{}).get('Url',''))
|
||||||
|
|
||||||
|
|
||||||
|
elif c.get('Actions', {}).get('Browse',{}).get('Url',''):
|
||||||
|
title = c.get('Title', c.get('AccessibilityTitle'))
|
||||||
|
itm = item.clone(title = title,
|
||||||
|
url = c.get('Actions', {}).get('Browse',{}).get('Url',''))
|
||||||
|
|
||||||
|
|
||||||
|
itemlist.append(itm)
|
||||||
|
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def findvideos(item):
|
||||||
|
item.action = 'play'
|
||||||
|
|
||||||
|
js = httptools.downloadpage(item.url, cloudscraper=True).json.get('body', {})
|
||||||
|
video_urls = list()
|
||||||
|
for it in js:
|
||||||
|
video_urls.append(['m3u8 [{}]'.format(it.get('bitrate')), it.get('url')])
|
||||||
|
|
||||||
|
item.referer = False
|
||||||
|
item.server = 'directo'
|
||||||
|
item.video_urls = video_urls
|
||||||
|
return [item]
|
||||||
|
|||||||
+6
-4
@@ -287,10 +287,11 @@ def downloadpage(url, **opt):
|
|||||||
|
|
||||||
# Headers passed as parameters
|
# Headers passed as parameters
|
||||||
if opt.get('headers', None) is not None:
|
if opt.get('headers', None) is not None:
|
||||||
|
opt['headers'] = dict(opt['headers'])
|
||||||
if not opt.get('replace_headers', False):
|
if not opt.get('replace_headers', False):
|
||||||
req_headers.update(dict(opt['headers']))
|
req_headers.update(opt['headers'])
|
||||||
else:
|
else:
|
||||||
req_headers = dict(opt['headers'])
|
req_headers = opt['headers']
|
||||||
|
|
||||||
if domain in directIP.keys() and not opt.get('disable_directIP', False):
|
if domain in directIP.keys() and not opt.get('disable_directIP', False):
|
||||||
req_headers['Host'] = domain
|
req_headers['Host'] = domain
|
||||||
@@ -429,8 +430,9 @@ def downloadpage(url, **opt):
|
|||||||
else:
|
else:
|
||||||
logger.debug("CF retry with proxy for domain: %s" % domain)
|
logger.debug("CF retry with proxy for domain: %s" % domain)
|
||||||
if not opt.get('headers'):
|
if not opt.get('headers'):
|
||||||
opt['headers'] = []
|
opt['headers'] = {}
|
||||||
opt['headers'].extend([['Px-Host', domain], ['Px-Token', cf_proxy['token']]])
|
opt['headers']['Px-Host'] = domain
|
||||||
|
opt['headers']['Px-Token'] = cf_proxy['token']
|
||||||
opt['real-url'] = url
|
opt['real-url'] = url
|
||||||
ret = downloadpage(urlparse.urlunparse((parse.scheme, cf_proxy['url'], parse.path, parse.params, parse.query, parse.fragment)), **opt)
|
ret = downloadpage(urlparse.urlunparse((parse.scheme, cf_proxy['url'], parse.path, parse.params, parse.query, parse.fragment)), **opt)
|
||||||
ret.url = url
|
ret.url = url
|
||||||
|
|||||||
+1
-1
@@ -621,7 +621,7 @@ def scrape(func):
|
|||||||
else: autorenumber.start(itemlist)
|
else: autorenumber.start(itemlist)
|
||||||
|
|
||||||
if itemlist and action != 'play' and 'patronMenu' not in args and 'patronGenreMenu' not in args \
|
if itemlist and action != 'play' and 'patronMenu' not in args and 'patronGenreMenu' not in args \
|
||||||
and not stackCheck(['add_tvshow', 'get_newest']) and (function not in ['episodios', 'mainlist']
|
and not stackCheck(['add_tvshow', 'get_newest']) and not disabletmdb and (function not in ['episodios', 'mainlist']
|
||||||
or (function in ['episodios', 'seasons'] and config.get_setting('episode_info') and itemlist[0].season)):
|
or (function in ['episodios', 'seasons'] and config.get_setting('episode_info') and itemlist[0].season)):
|
||||||
# dbg()
|
# dbg()
|
||||||
tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
||||||
|
|||||||
+1
-1
@@ -120,7 +120,7 @@ def update_title(item):
|
|||||||
if item.from_title_tmdb: del item.from_title_tmdb
|
if item.from_title_tmdb: del item.from_title_tmdb
|
||||||
if not item.from_update and item.from_title: del item.from_title
|
if not item.from_update and item.from_title: del item.from_title
|
||||||
|
|
||||||
if item.contentSerieName: # We copy the title to serve as a reference in the "Complete Information" menu
|
if item.contentSerieName and item.contentType == 'tvshow': # We copy the title to serve as a reference in the "Complete Information" menu
|
||||||
item.infoLabels['originaltitle'] = item.contentSerieName
|
item.infoLabels['originaltitle'] = item.contentSerieName
|
||||||
item.contentTitle = item.contentSerieName
|
item.contentTitle = item.contentSerieName
|
||||||
else:
|
else:
|
||||||
|
|||||||
+11
-8
@@ -126,7 +126,7 @@ def HJs(val):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
message = 'your Python function failed! '
|
message = 'your Python function failed! '
|
||||||
try:
|
try:
|
||||||
message += e.message
|
message += str(e)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise MakeError('Error', message)
|
raise MakeError('Error', message)
|
||||||
@@ -319,7 +319,7 @@ class PyJs(object):
|
|||||||
#prop = prop.value
|
#prop = prop.value
|
||||||
if self.Class == 'Undefined' or self.Class == 'Null':
|
if self.Class == 'Undefined' or self.Class == 'Null':
|
||||||
raise MakeError('TypeError',
|
raise MakeError('TypeError',
|
||||||
'Undefined and null dont have properties!')
|
'Undefined and null dont have properties (tried getting property %s)' % repr(prop))
|
||||||
if not isinstance(prop, basestring):
|
if not isinstance(prop, basestring):
|
||||||
prop = prop.to_string().value
|
prop = prop.to_string().value
|
||||||
if not isinstance(prop, basestring): raise RuntimeError('Bug')
|
if not isinstance(prop, basestring): raise RuntimeError('Bug')
|
||||||
@@ -361,7 +361,7 @@ class PyJs(object):
|
|||||||
* / % + - << >> & ^ |'''
|
* / % + - << >> & ^ |'''
|
||||||
if self.Class == 'Undefined' or self.Class == 'Null':
|
if self.Class == 'Undefined' or self.Class == 'Null':
|
||||||
raise MakeError('TypeError',
|
raise MakeError('TypeError',
|
||||||
'Undefined and null dont have properties!')
|
'Undefined and null don\'t have properties (tried setting property %s)' % repr(prop))
|
||||||
if not isinstance(prop, basestring):
|
if not isinstance(prop, basestring):
|
||||||
prop = prop.to_string().value
|
prop = prop.to_string().value
|
||||||
if NUMPY_AVAILABLE and prop.isdigit():
|
if NUMPY_AVAILABLE and prop.isdigit():
|
||||||
@@ -991,7 +991,8 @@ class PyJs(object):
|
|||||||
cand = self.get(prop)
|
cand = self.get(prop)
|
||||||
if not cand.is_callable():
|
if not cand.is_callable():
|
||||||
raise MakeError('TypeError',
|
raise MakeError('TypeError',
|
||||||
'%s is not a function' % cand.typeof())
|
'%s is not a function (tried calling property %s of %s)' % (
|
||||||
|
cand.typeof(), repr(prop), repr(self.Class)))
|
||||||
return cand.call(self, args)
|
return cand.call(self, args)
|
||||||
|
|
||||||
def to_python(self):
|
def to_python(self):
|
||||||
@@ -1304,7 +1305,7 @@ class PyObjectWrapper(PyJs):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
message = 'your Python function failed! '
|
message = 'your Python function failed! '
|
||||||
try:
|
try:
|
||||||
message += e.message
|
message += str(e)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise MakeError('Error', message)
|
raise MakeError('Error', message)
|
||||||
@@ -1464,9 +1465,11 @@ class PyJsFunction(PyJs):
|
|||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
raise
|
raise
|
||||||
except RuntimeError as e: # maximum recursion
|
except RuntimeError as e: # maximum recursion
|
||||||
raise MakeError(
|
try:
|
||||||
'RangeError', e.message if
|
msg = e.message
|
||||||
not isinstance(e, NotImplementedError) else 'Not implemented!')
|
except:
|
||||||
|
msg = repr(e)
|
||||||
|
raise MakeError('RangeError', msg)
|
||||||
|
|
||||||
def has_instance(self, other):
|
def has_instance(self, other):
|
||||||
# I am not sure here so instanceof may not work lol.
|
# I am not sure here so instanceof may not work lol.
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
|
|||||||
mili = args[6].to_number() if l > 6 else Js(0)
|
mili = args[6].to_number() if l > 6 else Js(0)
|
||||||
if not y.is_nan() and 0 <= y.value <= 99:
|
if not y.is_nan() and 0 <= y.value <= 99:
|
||||||
y = y + Js(1900)
|
y = y + Js(1900)
|
||||||
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
|
return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
|
||||||
return PyJsDate(t, prototype=DatePrototype)
|
|
||||||
|
|
||||||
|
|
||||||
@Js
|
@Js
|
||||||
@@ -76,11 +75,12 @@ class PyJsDate(PyJs):
|
|||||||
|
|
||||||
# todo fix this problematic datetime part
|
# todo fix this problematic datetime part
|
||||||
def to_local_dt(self):
|
def to_local_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
UTCToLocal(self.value) // 1000)
|
seconds=UTCToLocal(self.value) // 1000)
|
||||||
|
|
||||||
def to_utc_dt(self):
|
def to_utc_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(self.value // 1000)
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
|
seconds=self.value // 1000)
|
||||||
|
|
||||||
def local_strftime(self, pattern):
|
def local_strftime(self, pattern):
|
||||||
if self.value is NaN:
|
if self.value is NaN:
|
||||||
@@ -118,21 +118,40 @@ class PyJsDate(PyJs):
|
|||||||
|
|
||||||
|
|
||||||
def parse_date(py_string): # todo support all date string formats
|
def parse_date(py_string): # todo support all date string formats
|
||||||
try:
|
date_formats = (
|
||||||
|
"%Y-%m-%d",
|
||||||
|
"%m/%d/%Y",
|
||||||
|
"%b %d %Y",
|
||||||
|
)
|
||||||
|
# Supports these hour formats and with or hour.
|
||||||
|
hour_formats = (
|
||||||
|
"T%H:%M:%S.%f",
|
||||||
|
"T%H:%M:%S",
|
||||||
|
) + ('',)
|
||||||
|
# Supports with or without Z indicator.
|
||||||
|
z_formats = ("Z",) + ('',)
|
||||||
|
supported_formats = [
|
||||||
|
d + t + z
|
||||||
|
for d in date_formats
|
||||||
|
for t in hour_formats
|
||||||
|
for z in z_formats
|
||||||
|
]
|
||||||
|
for date_format in supported_formats:
|
||||||
try:
|
try:
|
||||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
|
dt = datetime.datetime.strptime(py_string, date_format)
|
||||||
except:
|
except ValueError:
|
||||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
|
continue
|
||||||
return MakeDate(
|
else:
|
||||||
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
|
return MakeDate(
|
||||||
MakeTime(
|
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
|
||||||
Js(dt.hour), Js(dt.minute), Js(dt.second),
|
MakeTime(
|
||||||
Js(dt.microsecond // 1000)))
|
Js(dt.hour), Js(dt.minute), Js(dt.second),
|
||||||
except:
|
Js(dt.microsecond // 1000)))
|
||||||
raise MakeError(
|
|
||||||
'TypeError',
|
raise MakeError(
|
||||||
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
|
'TypeError',
|
||||||
% py_string)
|
'Could not parse date %s - unsupported date format. Currently only supported formats are RFC3339 utc, ISO Date, Short Date, and Long Date. Sorry!'
|
||||||
|
% py_string)
|
||||||
|
|
||||||
|
|
||||||
def date_constructor(*args):
|
def date_constructor(*args):
|
||||||
@@ -332,7 +351,7 @@ class DateProto:
|
|||||||
check_date(this)
|
check_date(this)
|
||||||
t = UTCToLocal(this.value)
|
t = UTCToLocal(this.value)
|
||||||
tim = MakeTime(
|
tim = MakeTime(
|
||||||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
|
||||||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
|
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
|
||||||
this.value = u
|
this.value = u
|
||||||
return u
|
return u
|
||||||
@@ -341,12 +360,164 @@ class DateProto:
|
|||||||
check_date(this)
|
check_date(this)
|
||||||
t = this.value
|
t = this.value
|
||||||
tim = MakeTime(
|
tim = MakeTime(
|
||||||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
|
||||||
u = TimeClip(MakeDate(Day(t), tim))
|
u = TimeClip(MakeDate(Day(t), tim))
|
||||||
this.value = u
|
this.value = u
|
||||||
return u
|
return u
|
||||||
|
|
||||||
# todo Complete all setters!
|
def setSeconds(sec, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(
|
||||||
|
Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCSeconds(sec, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(
|
||||||
|
Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setMinutes(min, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCMinutes(min, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setHours(hour, min=None, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
h = hour.to_number()
|
||||||
|
if not min is None: m = Js(MinFromTime(t))
|
||||||
|
else: m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(h, m, s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCHours(hour, min=None, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
h = hour.to_number()
|
||||||
|
if not min is None: m = Js(MinFromTime(t))
|
||||||
|
else: m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(h, m, s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setDate(date):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCDate(date):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setMonth(month, date=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCMonth(month, date=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setFullYear(year, month=None, date=None):
|
||||||
|
check_date(this)
|
||||||
|
if not this.value is NaN: t = UTCToLocal(this.value)
|
||||||
|
else: t = 0
|
||||||
|
y = year.to_number()
|
||||||
|
if not month is None: m = Js(MonthFromTime(t))
|
||||||
|
else: m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(y, m, dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCFullYear(year, month=None, date=None):
|
||||||
|
check_date(this)
|
||||||
|
if not this.value is NaN: t = UTCToLocal(this.value)
|
||||||
|
else: t = 0
|
||||||
|
y = year.to_number()
|
||||||
|
if not month is None: m = Js(MonthFromTime(t))
|
||||||
|
else: m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(y, m, dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
def toUTCString():
|
def toUTCString():
|
||||||
check_date(this)
|
check_date(this)
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ def DaylightSavingTA(t):
|
|||||||
return t
|
return t
|
||||||
try:
|
try:
|
||||||
return int(
|
return int(
|
||||||
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
|
LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
t // 1000)).seconds) * 1000
|
seconds=t // 1000)).seconds) * 1000
|
||||||
except:
|
except:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'Invalid datetime date, assumed DST time, may be inaccurate...',
|
'Invalid datetime date, assumed DST time, may be inaccurate...',
|
||||||
|
|||||||
+5
-12
@@ -53,7 +53,7 @@ def write_file_contents(path_or_file, contents):
|
|||||||
if hasattr(path_or_file, 'write'):
|
if hasattr(path_or_file, 'write'):
|
||||||
path_or_file.write(contents)
|
path_or_file.write(contents)
|
||||||
else:
|
else:
|
||||||
with open(path_as_local(path_or_file), 'w') as f:
|
with codecs.open(path_as_local(path_or_file), "w", "utf-8") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
|
|
||||||
|
|
||||||
@@ -238,6 +238,10 @@ class EvalJs(object):
|
|||||||
self.execute_debug(code)
|
self.execute_debug(code)
|
||||||
return self['PyJsEvalResult']
|
return self['PyJsEvalResult']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self):
|
||||||
|
return self._context
|
||||||
|
|
||||||
def __getattr__(self, var):
|
def __getattr__(self, var):
|
||||||
return getattr(self._var, var)
|
return getattr(self._var, var)
|
||||||
|
|
||||||
@@ -268,14 +272,3 @@ class EvalJs(object):
|
|||||||
else:
|
else:
|
||||||
sys.stderr.write('EXCEPTION: ' + str(e) + '\n')
|
sys.stderr.write('EXCEPTION: ' + str(e) + '\n')
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
#print x
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#with open('C:\Users\Piotrek\Desktop\esprima.js', 'rb') as f:
|
|
||||||
# x = f.read()
|
|
||||||
e = EvalJs()
|
|
||||||
e.execute('square(x)')
|
|
||||||
#e.execute(x)
|
|
||||||
e.console()
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ def console():
|
|||||||
|
|
||||||
@Js
|
@Js
|
||||||
def log():
|
def log():
|
||||||
print(arguments[0])
|
print(" ".join(repr(element) for element in arguments.to_list()))
|
||||||
|
|
||||||
console.put('log', log)
|
console.put('log', log)
|
||||||
console.put('debug', log)
|
console.put('debug', log)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .seval import eval_js_vm
|
||||||
@@ -602,11 +602,12 @@ class PyJsDate(PyJs):
|
|||||||
|
|
||||||
# todo fix this problematic datetime part
|
# todo fix this problematic datetime part
|
||||||
def to_local_dt(self):
|
def to_local_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
self.UTCToLocal(self.value) // 1000)
|
seconds=self.UTCToLocal(self.value) // 1000)
|
||||||
|
|
||||||
def to_utc_dt(self):
|
def to_utc_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(self.value // 1000)
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
|
seconds=self.value // 1000)
|
||||||
|
|
||||||
def local_strftime(self, pattern):
|
def local_strftime(self, pattern):
|
||||||
if self.value is NaN:
|
if self.value is NaN:
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
|
|||||||
mili = args[6].to_number() if l > 6 else Js(0)
|
mili = args[6].to_number() if l > 6 else Js(0)
|
||||||
if not y.is_nan() and 0 <= y.value <= 99:
|
if not y.is_nan() and 0 <= y.value <= 99:
|
||||||
y = y + Js(1900)
|
y = y + Js(1900)
|
||||||
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
|
return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
|
||||||
return PyJsDate(t, prototype=DatePrototype)
|
|
||||||
|
|
||||||
|
|
||||||
@Js
|
@Js
|
||||||
@@ -76,11 +75,12 @@ class PyJsDate(PyJs):
|
|||||||
|
|
||||||
# todo fix this problematic datetime part
|
# todo fix this problematic datetime part
|
||||||
def to_local_dt(self):
|
def to_local_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
UTCToLocal(self.value) // 1000)
|
seconds=UTCToLocal(self.value) // 1000)
|
||||||
|
|
||||||
def to_utc_dt(self):
|
def to_utc_dt(self):
|
||||||
return datetime.datetime.utcfromtimestamp(self.value // 1000)
|
return datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
|
seconds=self.value // 1000)
|
||||||
|
|
||||||
def local_strftime(self, pattern):
|
def local_strftime(self, pattern):
|
||||||
if self.value is NaN:
|
if self.value is NaN:
|
||||||
@@ -332,7 +332,7 @@ class DateProto:
|
|||||||
check_date(this)
|
check_date(this)
|
||||||
t = UTCToLocal(this.value)
|
t = UTCToLocal(this.value)
|
||||||
tim = MakeTime(
|
tim = MakeTime(
|
||||||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
|
||||||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
|
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
|
||||||
this.value = u
|
this.value = u
|
||||||
return u
|
return u
|
||||||
@@ -341,12 +341,164 @@ class DateProto:
|
|||||||
check_date(this)
|
check_date(this)
|
||||||
t = this.value
|
t = this.value
|
||||||
tim = MakeTime(
|
tim = MakeTime(
|
||||||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
|
||||||
u = TimeClip(MakeDate(Day(t), tim))
|
u = TimeClip(MakeDate(Day(t), tim))
|
||||||
this.value = u
|
this.value = u
|
||||||
return u
|
return u
|
||||||
|
|
||||||
# todo Complete all setters!
|
def setSeconds(sec, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(
|
||||||
|
Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCSeconds(sec, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(
|
||||||
|
Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setMinutes(min, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCMinutes(min, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setHours(hour, min=None, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
h = hour.to_number()
|
||||||
|
if not min is None: m = Js(MinFromTime(t))
|
||||||
|
else: m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(h, m, s, milli))
|
||||||
|
u = TimeClip(LocalToUTC(date))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCHours(hour, min=None, sec=None, ms=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
h = hour.to_number()
|
||||||
|
if not min is None: m = Js(MinFromTime(t))
|
||||||
|
else: m = min.to_number()
|
||||||
|
if not sec is None: s = Js(SecFromTime(t))
|
||||||
|
else: s = sec.to_number()
|
||||||
|
if not ms is None: milli = Js(msFromTime(t))
|
||||||
|
else: milli = ms.to_number()
|
||||||
|
date = MakeDate(Day(t), MakeTime(h, m, s, milli))
|
||||||
|
v = TimeClip(date)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setDate(date):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCDate(date):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setMonth(month, date=None):
|
||||||
|
check_date(this)
|
||||||
|
t = UTCToLocal(this.value)
|
||||||
|
m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCMonth(month, date=None):
|
||||||
|
check_date(this)
|
||||||
|
t = this.value
|
||||||
|
m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
|
def setFullYear(year, month=None, date=None):
|
||||||
|
check_date(this)
|
||||||
|
if not this.value is NaN: t = UTCToLocal(this.value)
|
||||||
|
else: t = 0
|
||||||
|
y = year.to_number()
|
||||||
|
if not month is None: m = Js(MonthFromTime(t))
|
||||||
|
else: m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(y, m, dt), TimeWithinDay(t))
|
||||||
|
u = TimeClip(LocalToUTC(newDate))
|
||||||
|
this.value = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
def setUTCFullYear(year, month=None, date=None):
|
||||||
|
check_date(this)
|
||||||
|
if not this.value is NaN: t = UTCToLocal(this.value)
|
||||||
|
else: t = 0
|
||||||
|
y = year.to_number()
|
||||||
|
if not month is None: m = Js(MonthFromTime(t))
|
||||||
|
else: m = month.to_number()
|
||||||
|
if not date is None: dt = Js(DateFromTime(t))
|
||||||
|
else: dt = date.to_number()
|
||||||
|
newDate = MakeDate(
|
||||||
|
MakeDay(y, m, dt), TimeWithinDay(t))
|
||||||
|
v = TimeClip(newDate)
|
||||||
|
this.value = v
|
||||||
|
return v
|
||||||
|
|
||||||
def toUTCString():
|
def toUTCString():
|
||||||
check_date(this)
|
check_date(this)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ CONSTANTS = {
|
|||||||
'SQRT1_2': 0.7071067811865476,
|
'SQRT1_2': 0.7071067811865476,
|
||||||
'SQRT2': 1.4142135623730951
|
'SQRT2': 1.4142135623730951
|
||||||
}
|
}
|
||||||
|
def is_infinity(x):
|
||||||
|
return x - 1e10 == x
|
||||||
|
|
||||||
class MathFunctions:
|
class MathFunctions:
|
||||||
def abs(this, args):
|
def abs(this, args):
|
||||||
@@ -65,22 +66,22 @@ class MathFunctions:
|
|||||||
def ceil(this, args):
|
def ceil(this, args):
|
||||||
x = get_arg(args, 0)
|
x = get_arg(args, 0)
|
||||||
a = to_number(x)
|
a = to_number(x)
|
||||||
if a != a: # it must be a nan
|
if not is_finite(x):
|
||||||
return NaN
|
return x
|
||||||
return float(math.ceil(a))
|
return float(math.ceil(a))
|
||||||
|
|
||||||
def floor(this, args):
|
def floor(this, args):
|
||||||
x = get_arg(args, 0)
|
x = get_arg(args, 0)
|
||||||
a = to_number(x)
|
a = to_number(x)
|
||||||
if a != a: # it must be a nan
|
if not is_finite(x):
|
||||||
return NaN
|
return x
|
||||||
return float(math.floor(a))
|
return float(math.floor(a))
|
||||||
|
|
||||||
def round(this, args):
|
def round(this, args):
|
||||||
x = get_arg(args, 0)
|
x = get_arg(args, 0)
|
||||||
a = to_number(x)
|
a = to_number(x)
|
||||||
if a != a: # it must be a nan
|
if not is_finite(x):
|
||||||
return NaN
|
return x
|
||||||
return float(round(a))
|
return float(round(a))
|
||||||
|
|
||||||
def sin(this, args):
|
def sin(this, args):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from ..conversions import *
|
from ..conversions import *
|
||||||
from ..func_utils import *
|
from ..func_utils import *
|
||||||
|
from six import unichr
|
||||||
|
|
||||||
def fromCharCode(this, args):
|
def fromCharCode(this, args):
|
||||||
res = u''
|
res = u''
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ def DaylightSavingTA(t):
|
|||||||
return t
|
return t
|
||||||
try:
|
try:
|
||||||
return int(
|
return int(
|
||||||
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
|
LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta(
|
||||||
t // 1000)).seconds) * 1000
|
seconds=t // 1000)).seconds) * 1000
|
||||||
except:
|
except:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'Invalid datetime date, assumed DST time, may be inaccurate...',
|
'Invalid datetime date, assumed DST time, may be inaccurate...',
|
||||||
|
|||||||
@@ -798,7 +798,7 @@ OP_CODES = {}
|
|||||||
g = ''
|
g = ''
|
||||||
for g in globals():
|
for g in globals():
|
||||||
try:
|
try:
|
||||||
if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE':
|
if not issubclass(globals()[g], OP_CODE) or g == 'OP_CODE':
|
||||||
continue
|
continue
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ def replacement_template(rep, source, span, npar):
|
|||||||
res += '$'
|
res += '$'
|
||||||
n += 2
|
n += 2
|
||||||
continue
|
continue
|
||||||
|
elif rep[n + 1] == '&':
|
||||||
|
# replace with matched string
|
||||||
|
res += source[span[0]:span[1]]
|
||||||
|
n += 2
|
||||||
|
continue
|
||||||
elif rep[n + 1] == '`':
|
elif rep[n + 1] == '`':
|
||||||
# replace with string that is BEFORE match
|
# replace with string that is BEFORE match
|
||||||
res += source[:span[0]]
|
res += source[:span[0]]
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from timeit import timeit
|
from timeit import timeit
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from array import array
|
from array import array
|
||||||
from itertools import izip
|
try:
|
||||||
|
#python 2 code
|
||||||
|
from itertools import izip as zip
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +54,7 @@ t = []
|
|||||||
|
|
||||||
Type = None
|
Type = None
|
||||||
try:
|
try:
|
||||||
print timeit(
|
print(timeit(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
t.append(4)
|
t.append(4)
|
||||||
@@ -56,7 +63,7 @@ t.pop()
|
|||||||
|
|
||||||
|
|
||||||
""",
|
""",
|
||||||
"from __main__ import X,Y,namedtuple,array,t,add,Type, izip",
|
"from __main__ import X,Y,namedtuple,array,t,add,Type, zip",
|
||||||
number=1000000)
|
number=1000000))
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import print_function
|
||||||
from string import ascii_lowercase, digits
|
from string import ascii_lowercase, digits
|
||||||
##################################
|
##################################
|
||||||
StringName = u'PyJsConstantString%d_'
|
StringName = u'PyJsConstantString%d_'
|
||||||
@@ -305,4 +306,4 @@ if __name__ == '__main__':
|
|||||||
''')
|
''')
|
||||||
|
|
||||||
t, d = remove_constants(test)
|
t, d = remove_constants(test)
|
||||||
print t, d
|
print(t, d)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ If case of parsing errors it must return a pos of error.
|
|||||||
NOTES:
|
NOTES:
|
||||||
Strings and other literals are not present so each = means assignment
|
Strings and other literals are not present so each = means assignment
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from utils import *
|
from utils import *
|
||||||
from jsparser import *
|
from jsparser import *
|
||||||
|
|
||||||
@@ -80,4 +82,4 @@ def bass_translator(s):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print bass_translator('3.ddsd = 40')
|
print(bass_translator('3.ddsd = 40'))
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ FOR 123
|
|||||||
FOR iter
|
FOR iter
|
||||||
CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH
|
CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from utils import *
|
from utils import *
|
||||||
from jsparser import *
|
from jsparser import *
|
||||||
from nodevisitor import exp_translator
|
from nodevisitor import exp_translator
|
||||||
@@ -477,4 +479,4 @@ def translate_flow(source):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
#print do_dowhile('do {} while(k+f)', 0)[0]
|
#print do_dowhile('do {} while(k+f)', 0)[0]
|
||||||
#print 'e: "%s"'%do_expression('++(c?g:h); mj', 0)[0]
|
#print 'e: "%s"'%do_expression('++(c?g:h); mj', 0)[0]
|
||||||
print translate_flow('a; yimport test')[0]
|
print(translate_flow('a; yimport test')[0])
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""This module removes JS functions from source code"""
|
"""This module removes JS functions from source code"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from jsparser import *
|
from jsparser import *
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
@@ -94,5 +96,5 @@ def remove_functions(source, all_inline=False):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print remove_functions(
|
print(remove_functions(
|
||||||
'5+5 function n (functiona ,functionaj) {dsd s, dsdd}')
|
'5+5 function n (functiona ,functionaj) {dsd s, dsdd}'))
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ TODO
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ OP_METHODS = {
|
|||||||
|
|
||||||
def dbg(source):
|
def dbg(source):
|
||||||
try:
|
try:
|
||||||
with open('C:\Users\Piotrek\Desktop\dbg.py', 'w') as f:
|
with open(r'C:\Users\Piotrek\Desktop\dbg.py', 'w') as f:
|
||||||
f.write(source)
|
f.write(source)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -77,13 +78,13 @@ def indent(lines, ind=4):
|
|||||||
def inject_before_lval(source, lval, code):
|
def inject_before_lval(source, lval, code):
|
||||||
if source.count(lval) > 1:
|
if source.count(lval) > 1:
|
||||||
dbg(source)
|
dbg(source)
|
||||||
print
|
print()
|
||||||
print lval
|
print(lval)
|
||||||
raise RuntimeError('To many lvals (%s)' % lval)
|
raise RuntimeError('To many lvals (%s)' % lval)
|
||||||
elif not source.count(lval):
|
elif not source.count(lval):
|
||||||
dbg(source)
|
dbg(source)
|
||||||
print
|
print()
|
||||||
print lval
|
print(lval)
|
||||||
assert lval not in source
|
assert lval not in source
|
||||||
raise RuntimeError('No lval found "%s"' % lval)
|
raise RuntimeError('No lval found "%s"' % lval)
|
||||||
end = source.index(lval)
|
end = source.index(lval)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from jsparser import *
|
from jsparser import *
|
||||||
from utils import *
|
from utils import *
|
||||||
import re
|
import re
|
||||||
@@ -557,6 +559,6 @@ if __name__ == '__main__':
|
|||||||
#print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) .
|
#print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) .
|
||||||
# jiji (h , ji , i)(non )( )()()()')
|
# jiji (h , ji , i)(non )( )()()()')
|
||||||
for e in xrange(3):
|
for e in xrange(3):
|
||||||
print exp_translator('jk = kk.ik++')
|
print(exp_translator('jk = kk.ik++'))
|
||||||
#First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay!
|
#First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay!
|
||||||
print exp_translator('delete a.f')
|
print(exp_translator('delete a.f'))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
""" This module removes all objects/arrays from JS source code and replace them with LVALS.
|
""" This module removes all objects/arrays from JS source code and replace them with LVALS.
|
||||||
Also it has s function translating removed object/array to python code.
|
Also it has s function translating removed object/array to python code.
|
||||||
Use this module just after removing constants. Later move on to removing functions"""
|
Use this module just after removing constants. Later move on to removing functions"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
OBJECT_LVAL = 'PyJsLvalObject%d_'
|
OBJECT_LVAL = 'PyJsLvalObject%d_'
|
||||||
ARRAY_LVAL = 'PyJsLvalArray%d_'
|
ARRAY_LVAL = 'PyJsLvalArray%d_'
|
||||||
from utils import *
|
from utils import *
|
||||||
@@ -180,7 +182,7 @@ def translate_object(obj, lval, obj_count=1, arr_count=1):
|
|||||||
try:
|
try:
|
||||||
key, value = spl
|
key, value = spl
|
||||||
except: #len(spl)> 2
|
except: #len(spl)> 2
|
||||||
print 'Unusual case ' + repr(e)
|
print('Unusual case ' + repr(e))
|
||||||
key = spl[0]
|
key = spl[0]
|
||||||
value = ':'.join(spl[1:])
|
value = ':'.join(spl[1:])
|
||||||
key = key.strip()
|
key = key.strip()
|
||||||
@@ -293,8 +295,8 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
#print remove_objects(test)
|
#print remove_objects(test)
|
||||||
#print list(bracket_split(' {}'))
|
#print list(bracket_split(' {}'))
|
||||||
print
|
print()
|
||||||
print remove_arrays(
|
print(remove_arrays(
|
||||||
'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])'
|
'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])'
|
||||||
)
|
))
|
||||||
print is_object('', ')')
|
print(is_object('', ')'))
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from flow import translate_flow
|
from flow import translate_flow
|
||||||
from constants import remove_constants, recover_constants
|
from constants import remove_constants, recover_constants
|
||||||
from objects import remove_objects, remove_arrays, translate_object, translate_array, set_func_translator
|
from objects import remove_objects, remove_arrays, translate_object, translate_array, set_func_translator
|
||||||
@@ -148,4 +150,4 @@ if __name__ == '__main__':
|
|||||||
#res = translate_js(jq)
|
#res = translate_js(jq)
|
||||||
res = translate_js(t)
|
res = translate_js(t)
|
||||||
dbg(SANDBOX % indent(res))
|
dbg(SANDBOX % indent(res))
|
||||||
print 'Done'
|
print('Done')
|
||||||
|
|||||||
+34
-12
@@ -1,10 +1,16 @@
|
|||||||
__all__ = ['require']
|
__all__ = ['require']
|
||||||
|
|
||||||
import subprocess, os, codecs, glob
|
import subprocess, os, codecs, glob
|
||||||
from .evaljs import translate_js, DEFAULT_HEADER
|
from .evaljs import translate_js, DEFAULT_HEADER
|
||||||
|
from .translators.friendly_nodes import is_valid_py_name
|
||||||
import six
|
import six
|
||||||
|
import tempfile
|
||||||
|
import hashlib
|
||||||
|
import random
|
||||||
|
|
||||||
DID_INIT = False
|
DID_INIT = False
|
||||||
DIRNAME = os.path.dirname(os.path.abspath(__file__))
|
DIRNAME = tempfile.mkdtemp()
|
||||||
PY_NODE_MODULES_PATH = os.path.join(DIRNAME, 'py_node_modules')
|
PY_NODE_MODULES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py_node_modules')
|
||||||
|
|
||||||
|
|
||||||
def _init():
|
def _init():
|
||||||
@@ -46,23 +52,33 @@ GET_FROM_GLOBALS_FUNC = '''
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def _get_module_py_name(module_name):
|
def _get_module_py_name(module_name):
|
||||||
return module_name.replace('-', '_')
|
return module_name.replace('-', '_')
|
||||||
|
|
||||||
|
|
||||||
def _get_module_var_name(module_name):
|
def _get_module_var_name(module_name):
|
||||||
return _get_module_py_name(module_name).rpartition('/')[-1]
|
cand = _get_module_py_name(module_name).rpartition('/')[-1]
|
||||||
|
if not is_valid_py_name(cand):
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid Python module name %s (generated from %s). Unsupported/invalid npm module specification?" % (
|
||||||
|
repr(cand), repr(module_name)))
|
||||||
|
return cand
|
||||||
|
|
||||||
|
|
||||||
def _get_and_translate_npm_module(module_name, include_polyfill=False, update=False):
|
def _get_and_translate_npm_module(module_name, include_polyfill=False, update=False, maybe_version_str=""):
|
||||||
assert isinstance(module_name, str), 'module_name must be a string!'
|
assert isinstance(module_name, str), 'module_name must be a string!'
|
||||||
|
|
||||||
py_name = _get_module_py_name(module_name)
|
py_name = _get_module_py_name(module_name)
|
||||||
module_filename = '%s.py' % py_name
|
module_filename = '%s.py' % py_name
|
||||||
var_name = _get_module_var_name(module_name)
|
var_name = _get_module_var_name(module_name)
|
||||||
if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH,
|
if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH,
|
||||||
module_filename)) or update:
|
module_filename)) or update:
|
||||||
_init()
|
_init()
|
||||||
in_file_name = 'tmp0in439341018923js2py.js'
|
module_hash = hashlib.sha1(module_name.encode("utf-8")).hexdigest()[:15]
|
||||||
out_file_name = 'tmp0out439341018923js2py.js'
|
version = random.randrange(10000000000000)
|
||||||
|
in_file_name = 'in_%s_%d.js' % (module_hash, version)
|
||||||
|
out_file_name = 'out_%s_%d.js' % (module_hash, version)
|
||||||
code = ADD_TO_GLOBALS_FUNC
|
code = ADD_TO_GLOBALS_FUNC
|
||||||
if include_polyfill:
|
if include_polyfill:
|
||||||
code += "\n;require('babel-polyfill');\n"
|
code += "\n;require('babel-polyfill');\n"
|
||||||
@@ -74,6 +90,8 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa
|
|||||||
f.write(code.encode('utf-8') if six.PY3 else code)
|
f.write(code.encode('utf-8') if six.PY3 else code)
|
||||||
|
|
||||||
pkg_name = module_name.partition('/')[0]
|
pkg_name = module_name.partition('/')[0]
|
||||||
|
if maybe_version_str:
|
||||||
|
pkg_name += '@' + maybe_version_str
|
||||||
# make sure the module is installed
|
# make sure the module is installed
|
||||||
assert subprocess.call(
|
assert subprocess.call(
|
||||||
'cd %s;npm install %s' % (repr(DIRNAME), pkg_name),
|
'cd %s;npm install %s' % (repr(DIRNAME), pkg_name),
|
||||||
@@ -93,7 +111,7 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa
|
|||||||
with codecs.open(os.path.join(DIRNAME, out_file_name), "r",
|
with codecs.open(os.path.join(DIRNAME, out_file_name), "r",
|
||||||
"utf-8") as f:
|
"utf-8") as f:
|
||||||
js_code = f.read()
|
js_code = f.read()
|
||||||
os.remove(os.path.join(DIRNAME, out_file_name))
|
print("Bundled JS library dumped at: %s" % os.path.join(DIRNAME, out_file_name))
|
||||||
if len(js_code) < 50:
|
if len(js_code) < 50:
|
||||||
raise RuntimeError("Candidate JS bundle too short - likely browserify issue.")
|
raise RuntimeError("Candidate JS bundle too short - likely browserify issue.")
|
||||||
js_code += GET_FROM_GLOBALS_FUNC
|
js_code += GET_FROM_GLOBALS_FUNC
|
||||||
@@ -117,21 +135,25 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa
|
|||||||
return py_code
|
return py_code
|
||||||
|
|
||||||
|
|
||||||
def require(module_name, include_polyfill=False, update=False, context=None):
|
def require(module_name, include_polyfill=True, update=False, context=None):
|
||||||
"""
|
"""
|
||||||
Installs the provided npm module, exports a js bundle via browserify, converts to ECMA 5.1 via babel and
|
Installs the provided npm module, exports a js bundle via browserify, converts to ECMA 5.1 via babel and
|
||||||
finally translates the generated JS bundle to Python via Js2Py.
|
finally translates the generated JS bundle to Python via Js2Py.
|
||||||
Returns a pure python object that behaves like the installed module. Nice!
|
Returns a pure python object that behaves like the installed module. Nice!
|
||||||
|
|
||||||
:param module_name: Name of the npm module to require. For example 'esprima'.
|
:param module_name: Name of the npm module to require. For example 'esprima'. Supports specific versions via @
|
||||||
|
specification. Eg: 'crypto-js@3.3'.
|
||||||
:param include_polyfill: Whether the babel-polyfill should be included as part of the translation. May be needed
|
:param include_polyfill: Whether the babel-polyfill should be included as part of the translation. May be needed
|
||||||
for some modules that use unsupported features.
|
for some modules that use unsupported features of JS6 such as Map or typed arrays.
|
||||||
:param update: Whether to force update the translation. Otherwise uses a cached version if exists.
|
:param update: Whether to force update the translation. Otherwise uses a cached version if exists.
|
||||||
:param context: Optional context in which the translated module should be executed in. If provided, the
|
:param context: Optional context in which the translated module should be executed in. If provided, the
|
||||||
header (js2py imports) will be skipped as it is assumed that the context already has all the necessary imports.
|
header (js2py imports) will be skipped as it is assumed that the context already has all the necessary imports.
|
||||||
:return: The JsObjectWrapper containing the translated module object. Can be used like a standard python object.
|
:return: The JsObjectWrapper containing the translated module object. Can be used like a standard python object.
|
||||||
"""
|
"""
|
||||||
py_code = _get_and_translate_npm_module(module_name, include_polyfill=include_polyfill, update=update)
|
module_name, maybe_version = (module_name+"@@@").split('@')[:2]
|
||||||
|
|
||||||
|
py_code = _get_and_translate_npm_module(module_name, include_polyfill=include_polyfill, update=update,
|
||||||
|
maybe_version_str=maybe_version)
|
||||||
# this is a bit hacky but we need to strip the default header from the generated code...
|
# this is a bit hacky but we need to strip the default header from the generated code...
|
||||||
if context is not None:
|
if context is not None:
|
||||||
if not py_code.startswith(DEFAULT_HEADER):
|
if not py_code.startswith(DEFAULT_HEADER):
|
||||||
@@ -141,5 +163,5 @@ def require(module_name, include_polyfill=False, update=False, context=None):
|
|||||||
assert py_code.startswith(DEFAULT_HEADER), "Unexpected header."
|
assert py_code.startswith(DEFAULT_HEADER), "Unexpected header."
|
||||||
py_code = py_code[len(DEFAULT_HEADER):]
|
py_code = py_code[len(DEFAULT_HEADER):]
|
||||||
context = {} if context is None else context
|
context = {} if context is None else context
|
||||||
exec (py_code, context)
|
exec(py_code, context)
|
||||||
return context['var'][_get_module_var_name(module_name)].to_py()
|
return context['var'][_get_module_var_name(module_name)].to_py()
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ def replacement_template(rep, source, span, npar):
|
|||||||
res += '$'
|
res += '$'
|
||||||
n += 2
|
n += 2
|
||||||
continue
|
continue
|
||||||
|
elif rep[n + 1] == '&':
|
||||||
|
# replace with matched string
|
||||||
|
res += source[span[0]:span[1]]
|
||||||
|
n += 2
|
||||||
|
continue
|
||||||
elif rep[n + 1] == '`':
|
elif rep[n + 1] == '`':
|
||||||
# replace with string that is BEFORE match
|
# replace with string that is BEFORE match
|
||||||
res += source[:span[0]]
|
res += source[:span[0]]
|
||||||
|
|||||||
@@ -14,26 +14,36 @@ if six.PY3:
|
|||||||
LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :)
|
LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :)
|
||||||
|
|
||||||
|
|
||||||
class ForController:
|
class LoopController:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.inside = [False]
|
self.update = [""]
|
||||||
self.update = ''
|
self.label_to_update_idx = {}
|
||||||
|
|
||||||
def enter_for(self, update):
|
def enter(self, update=""):
|
||||||
self.inside.append(True)
|
self.update.append(update)
|
||||||
self.update = update
|
|
||||||
|
|
||||||
def leave_for(self):
|
def leave(self):
|
||||||
self.inside.pop()
|
self.update.pop()
|
||||||
|
|
||||||
|
def get_update(self, label=None):
|
||||||
|
if label is None:
|
||||||
|
return self.update[-1]
|
||||||
|
if label not in self.label_to_update_idx:
|
||||||
|
raise SyntaxError("Undefined label %s" % label)
|
||||||
|
if self.label_to_update_idx[label] >= len(self.update):
|
||||||
|
raise SyntaxError("%s is not a iteration statement label?" % label)
|
||||||
|
return self.update[self.label_to_update_idx[label]]
|
||||||
|
|
||||||
|
def register_label(self, label):
|
||||||
|
if label in self.label_to_update_idx:
|
||||||
|
raise SyntaxError("label %s already used")
|
||||||
|
self.label_to_update_idx[label] = len(self.update)
|
||||||
|
|
||||||
|
def deregister_label(self, label):
|
||||||
|
del self.label_to_update_idx[label]
|
||||||
|
|
||||||
def enter_other(self):
|
|
||||||
self.inside.append(False)
|
|
||||||
|
|
||||||
def leave_other(self):
|
|
||||||
self.inside.pop()
|
|
||||||
|
|
||||||
def is_inside(self):
|
|
||||||
return self.inside[-1]
|
|
||||||
|
|
||||||
|
|
||||||
class InlineStack:
|
class InlineStack:
|
||||||
@@ -86,9 +96,10 @@ class ContextStack:
|
|||||||
|
|
||||||
|
|
||||||
def clean_stacks():
|
def clean_stacks():
|
||||||
global Context, inline_stack
|
global Context, inline_stack, loop_controller
|
||||||
Context = ContextStack()
|
Context = ContextStack()
|
||||||
inline_stack = InlineStack()
|
inline_stack = InlineStack()
|
||||||
|
loop_controller = LoopController()
|
||||||
|
|
||||||
|
|
||||||
def to_key(literal_or_identifier):
|
def to_key(literal_or_identifier):
|
||||||
@@ -108,6 +119,13 @@ def to_key(literal_or_identifier):
|
|||||||
else:
|
else:
|
||||||
return unicode(k)
|
return unicode(k)
|
||||||
|
|
||||||
|
def is_iteration_statement(cand):
|
||||||
|
if not isinstance(cand, dict):
|
||||||
|
# Multiple statements.
|
||||||
|
return False
|
||||||
|
return cand.get("type", "?") in {"ForStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def trans(ele, standard=False):
|
def trans(ele, standard=False):
|
||||||
"""Translates esprima syntax tree to python by delegating to appropriate translating node"""
|
"""Translates esprima syntax tree to python by delegating to appropriate translating node"""
|
||||||
@@ -367,9 +385,14 @@ def BreakStatement(type, label):
|
|||||||
|
|
||||||
def ContinueStatement(type, label):
|
def ContinueStatement(type, label):
|
||||||
if label:
|
if label:
|
||||||
return 'raise %s("Continued")\n' % (get_continue_label(label['name']))
|
maybe_update_expr = loop_controller.get_update(label=label['name'])
|
||||||
|
continue_stmt = 'raise %s("Continued")\n' % (get_continue_label(label['name']))
|
||||||
else:
|
else:
|
||||||
return 'continue\n'
|
maybe_update_expr = loop_controller.get_update()
|
||||||
|
continue_stmt = "continue\n"
|
||||||
|
if maybe_update_expr:
|
||||||
|
return "# continue update\n%s\n%s" % (maybe_update_expr, continue_stmt)
|
||||||
|
return continue_stmt
|
||||||
|
|
||||||
|
|
||||||
def ReturnStatement(type, argument):
|
def ReturnStatement(type, argument):
|
||||||
@@ -386,24 +409,28 @@ def DebuggerStatement(type):
|
|||||||
|
|
||||||
|
|
||||||
def DoWhileStatement(type, body, test):
|
def DoWhileStatement(type, body, test):
|
||||||
inside = trans(body) + 'if not %s:\n' % trans(test) + indent('break\n')
|
loop_controller.enter()
|
||||||
|
body_code = trans(body)
|
||||||
|
loop_controller.leave()
|
||||||
|
inside = body_code + 'if not %s:\n' % trans(test) + indent('break\n')
|
||||||
result = 'while 1:\n' + indent(inside)
|
result = 'while 1:\n' + indent(inside)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def ForStatement(type, init, test, update, body):
|
def ForStatement(type, init, test, update, body):
|
||||||
update = indent(trans(update)) if update else ''
|
update = trans(update) if update else ''
|
||||||
init = trans(init) if init else ''
|
init = trans(init) if init else ''
|
||||||
if not init.endswith('\n'):
|
if not init.endswith('\n'):
|
||||||
init += '\n'
|
init += '\n'
|
||||||
test = trans(test) if test else '1'
|
test = trans(test) if test else '1'
|
||||||
|
loop_controller.enter(update)
|
||||||
if not update:
|
if not update:
|
||||||
result = '#for JS loop\n%swhile %s:\n%s%s\n' % (
|
result = '#for JS loop\n%swhile %s:\n%s%s\n' % (
|
||||||
init, test, indent(trans(body)), update)
|
init, test, indent(trans(body)), update)
|
||||||
else:
|
else:
|
||||||
result = '#for JS loop\n%swhile %s:\n' % (init, test)
|
result = '#for JS loop\n%swhile %s:\n' % (init, test)
|
||||||
body = 'try:\n%sfinally:\n %s\n' % (indent(trans(body)), update)
|
result += indent("%s# update\n%s\n" % (trans(body), update))
|
||||||
result += indent(body)
|
loop_controller.leave()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -422,7 +449,9 @@ def ForInStatement(type, left, right, body, each):
|
|||||||
name = left['name']
|
name = left['name']
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Unusual ForIn loop')
|
raise RuntimeError('Unusual ForIn loop')
|
||||||
|
loop_controller.enter()
|
||||||
res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
|
res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
|
||||||
|
loop_controller.leave()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@@ -438,20 +467,23 @@ def IfStatement(type, test, consequent, alternate):
|
|||||||
|
|
||||||
def LabeledStatement(type, label, body):
|
def LabeledStatement(type, label, body):
|
||||||
# todo consider using smarter approach!
|
# todo consider using smarter approach!
|
||||||
|
label_name = label['name']
|
||||||
|
loop_controller.register_label(label_name)
|
||||||
inside = trans(body)
|
inside = trans(body)
|
||||||
|
loop_controller.deregister_label(label_name)
|
||||||
defs = ''
|
defs = ''
|
||||||
if inside.startswith('while ') or inside.startswith(
|
if is_iteration_statement(body) and (inside.startswith('while ') or inside.startswith(
|
||||||
'for ') or inside.startswith('#for'):
|
'for ') or inside.startswith('#for')):
|
||||||
# we have to add contine label as well...
|
# we have to add contine label as well...
|
||||||
# 3 or 1 since #for loop type has more lines before real for.
|
# 3 or 1 since #for loop type has more lines before real for.
|
||||||
sep = 1 if not inside.startswith('#for') else 3
|
sep = 1 if not inside.startswith('#for') else 3
|
||||||
cont_label = get_continue_label(label['name'])
|
cont_label = get_continue_label(label_name)
|
||||||
temp = inside.split('\n')
|
temp = inside.split('\n')
|
||||||
injected = 'try:\n' + '\n'.join(temp[sep:])
|
injected = 'try:\n' + '\n'.join(temp[sep:])
|
||||||
injected += 'except %s:\n pass\n' % cont_label
|
injected += 'except %s:\n pass\n' % cont_label
|
||||||
inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
|
inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
|
||||||
defs += 'class %s(Exception): pass\n' % cont_label
|
defs += 'class %s(Exception): pass\n' % cont_label
|
||||||
break_label = get_break_label(label['name'])
|
break_label = get_break_label(label_name)
|
||||||
inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label)
|
inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label)
|
||||||
defs += 'class %s(Exception): pass\n' % break_label
|
defs += 'class %s(Exception): pass\n' % break_label
|
||||||
return defs + inside
|
return defs + inside
|
||||||
@@ -546,7 +578,11 @@ def VariableDeclaration(type, declarations, kind):
|
|||||||
|
|
||||||
|
|
||||||
def WhileStatement(type, test, body):
|
def WhileStatement(type, test, body):
|
||||||
result = 'while %s:\n' % trans(test) + indent(trans(body))
|
test_code = trans(test)
|
||||||
|
loop_controller.enter()
|
||||||
|
body_code = trans(body)
|
||||||
|
loop_controller.leave()
|
||||||
|
result = 'while %s:\n' % test_code + indent(body_code)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -55,16 +55,19 @@ def dbg(x):
|
|||||||
"""does nothing, legacy dummy function"""
|
"""does nothing, legacy dummy function"""
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
# Another way of doing that would be with my auto esprima translation but its much slower:
|
||||||
|
# parsed = esprima.parse(js).to_dict()
|
||||||
|
def pyjsparser_parse_fn(code):
|
||||||
|
parser = pyjsparser.PyJsParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False):
|
def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False, parse_fn=pyjsparser_parse_fn):
|
||||||
"""js has to be a javascript source code.
|
"""js has to be a javascript source code.
|
||||||
returns equivalent python code."""
|
returns equivalent python code."""
|
||||||
if use_compilation_plan and not '//' in js and not '/*' in js:
|
if use_compilation_plan and not '//' in js and not '/*' in js:
|
||||||
return translate_js_with_compilation_plan(js, HEADER=HEADER)
|
return translate_js_with_compilation_plan(js, HEADER=HEADER)
|
||||||
parser = pyjsparser.PyJsParser()
|
|
||||||
parsed = parser.parse(js) # js to esprima syntax tree
|
parsed = parse_fn(js)
|
||||||
# Another way of doing that would be with my auto esprima translation but its much slower and causes import problems:
|
|
||||||
# parsed = esprima.parse(js).to_dict()
|
|
||||||
translating_nodes.clean_stacks()
|
translating_nodes.clean_stacks()
|
||||||
return HEADER + translating_nodes.trans(
|
return HEADER + translating_nodes.trans(
|
||||||
parsed) # syntax tree to python code
|
parsed) # syntax tree to python code
|
||||||
|
|||||||
+135
-29
@@ -26,17 +26,19 @@ def fix_js_args(func):
|
|||||||
return func
|
return func
|
||||||
code = append_arguments(six.get_function_code(func), ('this', 'arguments'))
|
code = append_arguments(six.get_function_code(func), ('this', 'arguments'))
|
||||||
|
|
||||||
return types.FunctionType(
|
result = types.FunctionType(
|
||||||
code,
|
code,
|
||||||
six.get_function_globals(func),
|
six.get_function_globals(func),
|
||||||
func.__name__,
|
func.__name__,
|
||||||
closure=six.get_function_closure(func))
|
closure=six.get_function_closure(func))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def append_arguments(code_obj, new_locals):
|
def append_arguments(code_obj, new_locals):
|
||||||
co_varnames = code_obj.co_varnames # Old locals
|
co_varnames = code_obj.co_varnames # Old locals
|
||||||
co_names = code_obj.co_names # Old globals
|
co_names = code_obj.co_names # Old globals
|
||||||
co_names += tuple(e for e in new_locals if e not in co_names)
|
new_args = tuple(e for e in new_locals if e not in co_names)
|
||||||
|
co_names += new_args
|
||||||
co_argcount = code_obj.co_argcount # Argument count
|
co_argcount = code_obj.co_argcount # Argument count
|
||||||
co_code = code_obj.co_code # The actual bytecode as a string
|
co_code = code_obj.co_code # The actual bytecode as a string
|
||||||
|
|
||||||
@@ -76,26 +78,51 @@ def append_arguments(code_obj, new_locals):
|
|||||||
names_to_varnames = dict(
|
names_to_varnames = dict(
|
||||||
(co_names.index(name), varnames.index(name)) for name in new_locals)
|
(co_names.index(name), varnames.index(name)) for name in new_locals)
|
||||||
|
|
||||||
|
is_new_bytecode = sys.version_info >= (3, 11)
|
||||||
# Now we modify the actual bytecode
|
# Now we modify the actual bytecode
|
||||||
modified = []
|
modified = []
|
||||||
|
drop_future_cache = False
|
||||||
for inst in instructions(code_obj):
|
for inst in instructions(code_obj):
|
||||||
|
if is_new_bytecode and inst.opname == "CACHE":
|
||||||
|
assert inst.arg == 0
|
||||||
|
if not drop_future_cache:
|
||||||
|
modified.extend(write_instruction(inst.opcode, inst.arg))
|
||||||
|
else:
|
||||||
|
# We need to inject NOOP to not break jumps :(
|
||||||
|
modified.extend(write_instruction(dis.opmap["NOP"], 0))
|
||||||
|
|
||||||
|
continue
|
||||||
op, arg = inst.opcode, inst.arg
|
op, arg = inst.opcode, inst.arg
|
||||||
# If the instruction is a LOAD_GLOBAL, we have to check to see if
|
# If the instruction is a LOAD_GLOBAL, we have to check to see if
|
||||||
# it's one of the globals that we are replacing. Either way,
|
# it's one of the globals that we are replacing. Either way,
|
||||||
# update its arg using the appropriate dict.
|
# update its arg using the appropriate dict.
|
||||||
|
drop_future_cache = False
|
||||||
if inst.opcode == LOAD_GLOBAL:
|
if inst.opcode == LOAD_GLOBAL:
|
||||||
if inst.arg in names_to_varnames:
|
idx = inst.arg
|
||||||
|
if is_new_bytecode:
|
||||||
|
idx = idx // 2
|
||||||
|
if idx in names_to_varnames:
|
||||||
op = LOAD_FAST
|
op = LOAD_FAST
|
||||||
arg = names_to_varnames[inst.arg]
|
arg = names_to_varnames[idx]
|
||||||
elif inst.arg in name_translations:
|
# Cache is not present after LOAD_FAST and needs to be removed.
|
||||||
arg = name_translations[inst.arg]
|
drop_future_cache = True
|
||||||
|
elif idx in name_translations:
|
||||||
|
tgt = name_translations[idx]
|
||||||
|
if is_new_bytecode:
|
||||||
|
tgt = 2*tgt + (inst.arg % 2)
|
||||||
|
arg = tgt
|
||||||
else:
|
else:
|
||||||
raise ValueError("a name was lost in translation")
|
raise(ValueError("a name was lost in translation last instruction %s" % str(inst)))
|
||||||
# If it accesses co_varnames or co_names then update its argument.
|
# If it accesses co_varnames or co_names then update its argument.
|
||||||
elif inst.opcode in opcode.haslocal:
|
elif inst.opcode in opcode.haslocal:
|
||||||
arg = varname_translations[inst.arg]
|
arg = varname_translations[inst.arg]
|
||||||
elif inst.opcode in opcode.hasname:
|
elif inst.opcode in opcode.hasname:
|
||||||
|
# for example STORE_GLOBAL
|
||||||
arg = name_translations[inst.arg]
|
arg = name_translations[inst.arg]
|
||||||
|
elif is_new_bytecode and inst.opcode in opcode.hasfree:
|
||||||
|
# Python 3.11+ adds refs at the end (after locals), for whatever reason...
|
||||||
|
if inst.argval not in code_obj.co_varnames[:code_obj.co_argcount]: # we do not need to remap existing arguments, they are not shifted by new ones.
|
||||||
|
arg = inst.arg + len(new_locals)
|
||||||
modified.extend(write_instruction(op, arg))
|
modified.extend(write_instruction(op, arg))
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
code = ''.join(modified)
|
code = ''.join(modified)
|
||||||
@@ -113,23 +140,26 @@ def append_arguments(code_obj, new_locals):
|
|||||||
code_obj.co_filename, code_obj.co_name,
|
code_obj.co_filename, code_obj.co_name,
|
||||||
code_obj.co_firstlineno, code_obj.co_lnotab,
|
code_obj.co_firstlineno, code_obj.co_lnotab,
|
||||||
code_obj.co_freevars, code_obj.co_cellvars)
|
code_obj.co_freevars, code_obj.co_cellvars)
|
||||||
|
|
||||||
# Done modifying codestring - make the code object
|
# Done modifying codestring - make the code object
|
||||||
if hasattr(code_obj, "replace"):
|
if hasattr(code_obj, "replace"):
|
||||||
# Python 3.8+
|
# Python 3.8+
|
||||||
return code_obj.replace(
|
code_obj = code_obj.replace(
|
||||||
co_argcount=co_argcount + new_locals_len,
|
co_argcount=co_argcount + new_locals_len,
|
||||||
co_nlocals=code_obj.co_nlocals + new_locals_len,
|
co_nlocals=code_obj.co_nlocals + new_locals_len,
|
||||||
co_code=code,
|
co_code=code,
|
||||||
co_names=names,
|
co_names=names,
|
||||||
co_varnames=varnames)
|
co_varnames=varnames)
|
||||||
|
return code_obj
|
||||||
else:
|
else:
|
||||||
return types.CodeType(*args)
|
return types.CodeType(*args)
|
||||||
|
|
||||||
|
|
||||||
def instructions(code_obj):
|
def instructions(code_obj, show_cache=True):
|
||||||
# easy for python 3.4+
|
if sys.version_info >= (3, 11):
|
||||||
if sys.version_info >= (3, 4):
|
# Python 3.11 introduced "cache instructions", hidden by default.
|
||||||
|
for inst in dis.Bytecode(code_obj, show_caches=show_cache):
|
||||||
|
yield inst
|
||||||
|
elif sys.version_info >= (3, 4): # easy for python 3.4+
|
||||||
for inst in dis.Bytecode(code_obj):
|
for inst in dis.Bytecode(code_obj):
|
||||||
yield inst
|
yield inst
|
||||||
else:
|
else:
|
||||||
@@ -171,7 +201,7 @@ def write_instruction(op, arg):
|
|||||||
chr((arg >> 8) & 255)
|
chr((arg >> 8) & 255)
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid oparg: {0} is too large".format(oparg))
|
raise ValueError("Invalid oparg: {0} is too large".format(arg))
|
||||||
else: # python 3.6+ uses wordcode instead of bytecode and they already supply all the EXTENDEND_ARG ops :)
|
else: # python 3.6+ uses wordcode instead of bytecode and they already supply all the EXTENDEND_ARG ops :)
|
||||||
if arg is None:
|
if arg is None:
|
||||||
return [chr(op), 0]
|
return [chr(op), 0]
|
||||||
@@ -191,6 +221,7 @@ def write_instruction(op, arg):
|
|||||||
# raise ValueError("Invalid oparg: {0} is too large".format(oparg))
|
# raise ValueError("Invalid oparg: {0} is too large".format(oparg))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def check(code_obj):
|
def check(code_obj):
|
||||||
old_bytecode = code_obj.co_code
|
old_bytecode = code_obj.co_code
|
||||||
insts = list(instructions(code_obj))
|
insts = list(instructions(code_obj))
|
||||||
@@ -221,24 +252,99 @@ def check(code_obj):
|
|||||||
'Your python version made changes to the bytecode')
|
'Your python version made changes to the bytecode')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def signature(func):
|
||||||
|
code_obj = six.get_function_code(func)
|
||||||
|
return (code_obj.co_nlocals, code_obj.co_argcount, code_obj.co_nlocals, code_obj.co_stacksize,
|
||||||
|
code_obj.co_flags, code_obj.co_names, code_obj.co_varnames,
|
||||||
|
code_obj.co_filename,
|
||||||
|
code_obj.co_freevars, code_obj.co_cellvars)
|
||||||
|
|
||||||
check(six.get_function_code(check))
|
check(six.get_function_code(check))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def compare_func(fake_func, gt_func):
|
||||||
|
print(signature(fake_func))
|
||||||
|
print(signature(gt_func))
|
||||||
|
assert signature(fake_func) == signature(gt_func)
|
||||||
|
fake_ins = list(instructions(six.get_function_code(fake_func), show_cache=False))
|
||||||
|
real_ins = list(instructions(six.get_function_code(gt_func), show_cache=False))
|
||||||
|
offset = 0
|
||||||
|
pos = 0
|
||||||
|
for e in fake_ins:
|
||||||
|
if e.opname == "NOP":
|
||||||
|
offset += 1 # ignore NOPs that are inserted in place of old cache.
|
||||||
|
else:
|
||||||
|
real = real_ins[pos]
|
||||||
|
fake = e
|
||||||
|
print("POS %d OFFSET: %d FAKE VS REAL" % (pos, offset))
|
||||||
|
print(fake)
|
||||||
|
print(real)
|
||||||
|
assert fake.opcode == real.opcode
|
||||||
|
if fake.opcode in dis.hasjabs or fake.opcode in dis.hasjrel:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert fake.arg == real.arg
|
||||||
|
assert fake.argval == real.argval or fake.opname in ["LOAD_CONST"]
|
||||||
|
assert fake.is_jump_target == real.is_jump_target
|
||||||
|
|
||||||
|
pos += 1
|
||||||
|
assert pos == len(real_ins), (pos, len(real_ins))
|
||||||
|
print("DONE, looks good.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
x = 'Wrong'
|
import faulthandler
|
||||||
dick = 3000
|
|
||||||
|
|
||||||
def func(a):
|
faulthandler.enable()
|
||||||
print(x, y, z, a)
|
|
||||||
print(dick)
|
|
||||||
d = (x, )
|
|
||||||
for e in (e for e in x):
|
|
||||||
print(e)
|
|
||||||
return x, y, z
|
|
||||||
|
|
||||||
func2 = types.FunctionType(
|
def func(cmpfn):
|
||||||
append_arguments(six.get_function_code(func), ('x', 'y', 'z')),
|
if not this.Class in ('Array', 'Arguments'):
|
||||||
six.get_function_globals(func),
|
return this.to_object() # do nothing
|
||||||
func.__name__,
|
arr = []
|
||||||
closure=six.get_function_closure(func))
|
for i in xrange(len(this)):
|
||||||
args = (2, 2, 3, 4), 3, 4
|
arr.append(this.get(six.text_type(i)))
|
||||||
assert func2(1, *args) == args
|
|
||||||
|
if not arr:
|
||||||
|
return this
|
||||||
|
if not cmpfn.is_callable():
|
||||||
|
cmpfn = None
|
||||||
|
cmp = lambda a, b: sort_compare(a, b, cmpfn)
|
||||||
|
if six.PY3:
|
||||||
|
key = functools.cmp_to_key(cmp)
|
||||||
|
arr.sort(key=key)
|
||||||
|
else:
|
||||||
|
arr.sort(cmp=cmp)
|
||||||
|
for i in xrange(len(arr)):
|
||||||
|
this.put(six.text_type(i), arr[i])
|
||||||
|
|
||||||
|
return this
|
||||||
|
|
||||||
|
|
||||||
|
def func_gt(cmpfn, this, arguments):
|
||||||
|
if not this.Class in ('Array', 'Arguments'):
|
||||||
|
return this.to_object() # do nothing
|
||||||
|
arr = []
|
||||||
|
for i in xrange(len(this)):
|
||||||
|
arr.append(this.get(six.text_type(i)))
|
||||||
|
|
||||||
|
if not arr:
|
||||||
|
return this
|
||||||
|
if not cmpfn.is_callable():
|
||||||
|
cmpfn = None
|
||||||
|
cmp = lambda a, b: sort_compare(a, b, cmpfn)
|
||||||
|
if six.PY3:
|
||||||
|
key = functools.cmp_to_key(cmp)
|
||||||
|
arr.sort(key=key)
|
||||||
|
else:
|
||||||
|
arr.sort(cmp=cmp)
|
||||||
|
for i in xrange(len(arr)):
|
||||||
|
this.put(six.text_type(i), arr[i])
|
||||||
|
|
||||||
|
return this
|
||||||
|
|
||||||
|
|
||||||
|
func2 = fix_js_args(func)
|
||||||
|
compare_func(func2, func_gt)
|
||||||
|
|||||||
@@ -0,0 +1,546 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"Makes working with XML feel like you are working with JSON"
|
||||||
|
|
||||||
|
try:
|
||||||
|
from defusedexpat import pyexpat as expat
|
||||||
|
except ImportError:
|
||||||
|
from xml.parsers import expat
|
||||||
|
from xml.sax.saxutils import XMLGenerator
|
||||||
|
from xml.sax.xmlreader import AttributesImpl
|
||||||
|
try: # pragma no cover
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError: # pragma no cover
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
_dict = dict
|
||||||
|
import platform
|
||||||
|
if tuple(map(int, platform.python_version_tuple()[:2])) < (3, 7):
|
||||||
|
from collections import OrderedDict as _dict
|
||||||
|
|
||||||
|
from inspect import isgenerator
|
||||||
|
|
||||||
|
try: # pragma no cover
|
||||||
|
_basestring = basestring
|
||||||
|
except NameError: # pragma no cover
|
||||||
|
_basestring = str
|
||||||
|
try: # pragma no cover
|
||||||
|
_unicode = unicode
|
||||||
|
except NameError: # pragma no cover
|
||||||
|
_unicode = str
|
||||||
|
|
||||||
|
__author__ = 'Martin Blech'
|
||||||
|
__version__ = '0.13.0'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingInterrupted(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _DictSAXHandler(object):
|
||||||
|
def __init__(self,
|
||||||
|
item_depth=0,
|
||||||
|
item_callback=lambda *args: True,
|
||||||
|
xml_attribs=True,
|
||||||
|
attr_prefix='@',
|
||||||
|
cdata_key='#text',
|
||||||
|
force_cdata=False,
|
||||||
|
cdata_separator='',
|
||||||
|
postprocessor=None,
|
||||||
|
dict_constructor=_dict,
|
||||||
|
strip_whitespace=True,
|
||||||
|
namespace_separator=':',
|
||||||
|
namespaces=None,
|
||||||
|
force_list=None,
|
||||||
|
comment_key='#comment'):
|
||||||
|
self.path = []
|
||||||
|
self.stack = []
|
||||||
|
self.data = []
|
||||||
|
self.item = None
|
||||||
|
self.item_depth = item_depth
|
||||||
|
self.xml_attribs = xml_attribs
|
||||||
|
self.item_callback = item_callback
|
||||||
|
self.attr_prefix = attr_prefix
|
||||||
|
self.cdata_key = cdata_key
|
||||||
|
self.force_cdata = force_cdata
|
||||||
|
self.cdata_separator = cdata_separator
|
||||||
|
self.postprocessor = postprocessor
|
||||||
|
self.dict_constructor = dict_constructor
|
||||||
|
self.strip_whitespace = strip_whitespace
|
||||||
|
self.namespace_separator = namespace_separator
|
||||||
|
self.namespaces = namespaces
|
||||||
|
self.namespace_declarations = dict_constructor()
|
||||||
|
self.force_list = force_list
|
||||||
|
self.comment_key = comment_key
|
||||||
|
|
||||||
|
def _build_name(self, full_name):
|
||||||
|
if self.namespaces is None:
|
||||||
|
return full_name
|
||||||
|
i = full_name.rfind(self.namespace_separator)
|
||||||
|
if i == -1:
|
||||||
|
return full_name
|
||||||
|
namespace, name = full_name[:i], full_name[i+1:]
|
||||||
|
try:
|
||||||
|
short_namespace = self.namespaces[namespace]
|
||||||
|
except KeyError:
|
||||||
|
short_namespace = namespace
|
||||||
|
if not short_namespace:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
return self.namespace_separator.join((short_namespace, name))
|
||||||
|
|
||||||
|
def _attrs_to_dict(self, attrs):
|
||||||
|
if isinstance(attrs, dict):
|
||||||
|
return attrs
|
||||||
|
return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
|
||||||
|
|
||||||
|
def startNamespaceDecl(self, prefix, uri):
|
||||||
|
self.namespace_declarations[prefix or ''] = uri
|
||||||
|
|
||||||
|
def startElement(self, full_name, attrs):
|
||||||
|
name = self._build_name(full_name)
|
||||||
|
attrs = self._attrs_to_dict(attrs)
|
||||||
|
if attrs and self.namespace_declarations:
|
||||||
|
attrs['xmlns'] = self.namespace_declarations
|
||||||
|
self.namespace_declarations = self.dict_constructor()
|
||||||
|
self.path.append((name, attrs or None))
|
||||||
|
if len(self.path) >= self.item_depth:
|
||||||
|
self.stack.append((self.item, self.data))
|
||||||
|
if self.xml_attribs:
|
||||||
|
attr_entries = []
|
||||||
|
for key, value in attrs.items():
|
||||||
|
key = self.attr_prefix+self._build_name(key)
|
||||||
|
if self.postprocessor:
|
||||||
|
entry = self.postprocessor(self.path, key, value)
|
||||||
|
else:
|
||||||
|
entry = (key, value)
|
||||||
|
if entry:
|
||||||
|
attr_entries.append(entry)
|
||||||
|
attrs = self.dict_constructor(attr_entries)
|
||||||
|
else:
|
||||||
|
attrs = None
|
||||||
|
self.item = attrs or None
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def endElement(self, full_name):
|
||||||
|
name = self._build_name(full_name)
|
||||||
|
if len(self.path) == self.item_depth:
|
||||||
|
item = self.item
|
||||||
|
if item is None:
|
||||||
|
item = (None if not self.data
|
||||||
|
else self.cdata_separator.join(self.data))
|
||||||
|
|
||||||
|
should_continue = self.item_callback(self.path, item)
|
||||||
|
if not should_continue:
|
||||||
|
raise ParsingInterrupted()
|
||||||
|
if self.stack:
|
||||||
|
data = (None if not self.data
|
||||||
|
else self.cdata_separator.join(self.data))
|
||||||
|
item = self.item
|
||||||
|
self.item, self.data = self.stack.pop()
|
||||||
|
if self.strip_whitespace and data and item:
|
||||||
|
data = data.strip() or None
|
||||||
|
if data and self.force_cdata and item is None:
|
||||||
|
item = self.dict_constructor()
|
||||||
|
if item is not None:
|
||||||
|
if data:
|
||||||
|
self.push_data(item, self.cdata_key, data)
|
||||||
|
self.item = self.push_data(self.item, name, item)
|
||||||
|
else:
|
||||||
|
self.item = self.push_data(self.item, name, data)
|
||||||
|
else:
|
||||||
|
self.item = None
|
||||||
|
self.data = []
|
||||||
|
self.path.pop()
|
||||||
|
|
||||||
|
def characters(self, data):
|
||||||
|
if not self.data:
|
||||||
|
self.data = [data]
|
||||||
|
else:
|
||||||
|
self.data.append(data)
|
||||||
|
|
||||||
|
def comments(self, data):
|
||||||
|
if self.strip_whitespace:
|
||||||
|
data = data.strip()
|
||||||
|
self.item = self.push_data(self.item, self.comment_key, data)
|
||||||
|
|
||||||
|
def push_data(self, item, key, data):
|
||||||
|
if self.postprocessor is not None:
|
||||||
|
result = self.postprocessor(self.path, key, data)
|
||||||
|
if result is None:
|
||||||
|
return item
|
||||||
|
key, data = result
|
||||||
|
if item is None:
|
||||||
|
item = self.dict_constructor()
|
||||||
|
try:
|
||||||
|
value = item[key]
|
||||||
|
if isinstance(value, list):
|
||||||
|
value.append(data)
|
||||||
|
else:
|
||||||
|
item[key] = [value, data]
|
||||||
|
except KeyError:
|
||||||
|
if self._should_force_list(key, data):
|
||||||
|
item[key] = [data]
|
||||||
|
else:
|
||||||
|
item[key] = data
|
||||||
|
return item
|
||||||
|
|
||||||
|
def _should_force_list(self, key, value):
|
||||||
|
if not self.force_list:
|
||||||
|
return False
|
||||||
|
if isinstance(self.force_list, bool):
|
||||||
|
return self.force_list
|
||||||
|
try:
|
||||||
|
return key in self.force_list
|
||||||
|
except TypeError:
|
||||||
|
return self.force_list(self.path[:-1], key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
|
||||||
|
namespace_separator=':', disable_entities=True, process_comments=False, **kwargs):
|
||||||
|
"""Parse the given XML input and convert it into a dictionary.
|
||||||
|
|
||||||
|
`xml_input` can either be a `string`, a file-like object, or a generator of strings.
|
||||||
|
|
||||||
|
If `xml_attribs` is `True`, element attributes are put in the dictionary
|
||||||
|
among regular child elements, using `@` as a prefix to avoid collisions. If
|
||||||
|
set to `False`, they are just ignored.
|
||||||
|
|
||||||
|
Simple example::
|
||||||
|
|
||||||
|
>>> import xmltodict
|
||||||
|
>>> doc = xmltodict.parse(\"\"\"
|
||||||
|
... <a prop="x">
|
||||||
|
... <b>1</b>
|
||||||
|
... <b>2</b>
|
||||||
|
... </a>
|
||||||
|
... \"\"\")
|
||||||
|
>>> doc['a']['@prop']
|
||||||
|
u'x'
|
||||||
|
>>> doc['a']['b']
|
||||||
|
[u'1', u'2']
|
||||||
|
|
||||||
|
If `item_depth` is `0`, the function returns a dictionary for the root
|
||||||
|
element (default behavior). Otherwise, it calls `item_callback` every time
|
||||||
|
an item at the specified depth is found and returns `None` in the end
|
||||||
|
(streaming mode).
|
||||||
|
|
||||||
|
The callback function receives two parameters: the `path` from the document
|
||||||
|
root to the item (name-attribs pairs), and the `item` (dict). If the
|
||||||
|
callback's return value is false-ish, parsing will be stopped with the
|
||||||
|
:class:`ParsingInterrupted` exception.
|
||||||
|
|
||||||
|
Streaming example::
|
||||||
|
|
||||||
|
>>> def handle(path, item):
|
||||||
|
... print('path:%s item:%s' % (path, item))
|
||||||
|
... return True
|
||||||
|
...
|
||||||
|
>>> xmltodict.parse(\"\"\"
|
||||||
|
... <a prop="x">
|
||||||
|
... <b>1</b>
|
||||||
|
... <b>2</b>
|
||||||
|
... </a>\"\"\", item_depth=2, item_callback=handle)
|
||||||
|
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
|
||||||
|
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
|
||||||
|
|
||||||
|
The optional argument `postprocessor` is a function that takes `path`,
|
||||||
|
`key` and `value` as positional arguments and returns a new `(key, value)`
|
||||||
|
pair where both `key` and `value` may have changed. Usage example::
|
||||||
|
|
||||||
|
>>> def postprocessor(path, key, value):
|
||||||
|
... try:
|
||||||
|
... return key + ':int', int(value)
|
||||||
|
... except (ValueError, TypeError):
|
||||||
|
... return key, value
|
||||||
|
>>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
|
||||||
|
... postprocessor=postprocessor)
|
||||||
|
{'a': {'b:int': [1, 2], 'b': 'x'}}
|
||||||
|
|
||||||
|
You can pass an alternate version of `expat` (such as `defusedexpat`) by
|
||||||
|
using the `expat` parameter. E.g:
|
||||||
|
|
||||||
|
>>> import defusedexpat
|
||||||
|
>>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
|
||||||
|
{'a': 'hello'}
|
||||||
|
|
||||||
|
You can use the force_list argument to force lists to be created even
|
||||||
|
when there is only a single child of a given level of hierarchy. The
|
||||||
|
force_list argument is a tuple of keys. If the key for a given level
|
||||||
|
of hierarchy is in the force_list argument, that level of hierarchy
|
||||||
|
will have a list as a child (even if there is only one sub-element).
|
||||||
|
The index_keys operation takes precedence over this. This is applied
|
||||||
|
after any user-supplied postprocessor has already run.
|
||||||
|
|
||||||
|
For example, given this input:
|
||||||
|
<servers>
|
||||||
|
<server>
|
||||||
|
<name>host1</name>
|
||||||
|
<os>Linux</os>
|
||||||
|
<interfaces>
|
||||||
|
<interface>
|
||||||
|
<name>em0</name>
|
||||||
|
<ip_address>10.0.0.1</ip_address>
|
||||||
|
</interface>
|
||||||
|
</interfaces>
|
||||||
|
</server>
|
||||||
|
</servers>
|
||||||
|
|
||||||
|
If called with force_list=('interface',), it will produce
|
||||||
|
this dictionary:
|
||||||
|
{'servers':
|
||||||
|
{'server':
|
||||||
|
{'name': 'host1',
|
||||||
|
'os': 'Linux'},
|
||||||
|
'interfaces':
|
||||||
|
{'interface':
|
||||||
|
[ {'name': 'em0', 'ip_address': '10.0.0.1' } ] } } }
|
||||||
|
|
||||||
|
`force_list` can also be a callable that receives `path`, `key` and
|
||||||
|
`value`. This is helpful in cases where the logic that decides whether
|
||||||
|
a list should be forced is more complex.
|
||||||
|
|
||||||
|
|
||||||
|
If `process_comment` is `True` then comment will be added with comment_key
|
||||||
|
(default=`'#comment'`) to then tag which contains comment
|
||||||
|
|
||||||
|
For example, given this input:
|
||||||
|
<a>
|
||||||
|
<b>
|
||||||
|
<!-- b comment -->
|
||||||
|
<c>
|
||||||
|
<!-- c comment -->
|
||||||
|
1
|
||||||
|
</c>
|
||||||
|
<d>2</d>
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
If called with process_comment=True, it will produce
|
||||||
|
this dictionary:
|
||||||
|
'a': {
|
||||||
|
'b': {
|
||||||
|
'#comment': 'b comment',
|
||||||
|
'c': {
|
||||||
|
|
||||||
|
'#comment': 'c comment',
|
||||||
|
'#text': '1',
|
||||||
|
},
|
||||||
|
'd': '2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
handler = _DictSAXHandler(namespace_separator=namespace_separator,
|
||||||
|
**kwargs)
|
||||||
|
if isinstance(xml_input, _unicode):
|
||||||
|
if not encoding:
|
||||||
|
encoding = 'utf-8'
|
||||||
|
xml_input = xml_input.encode(encoding)
|
||||||
|
if not process_namespaces:
|
||||||
|
namespace_separator = None
|
||||||
|
parser = expat.ParserCreate(
|
||||||
|
encoding,
|
||||||
|
namespace_separator
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
parser.ordered_attributes = True
|
||||||
|
except AttributeError:
|
||||||
|
# Jython's expat does not support ordered_attributes
|
||||||
|
pass
|
||||||
|
parser.StartNamespaceDeclHandler = handler.startNamespaceDecl
|
||||||
|
parser.StartElementHandler = handler.startElement
|
||||||
|
parser.EndElementHandler = handler.endElement
|
||||||
|
parser.CharacterDataHandler = handler.characters
|
||||||
|
if process_comments:
|
||||||
|
parser.CommentHandler = handler.comments
|
||||||
|
parser.buffer_text = True
|
||||||
|
if disable_entities:
|
||||||
|
try:
|
||||||
|
# Attempt to disable DTD in Jython's expat parser (Xerces-J).
|
||||||
|
feature = "http://apache.org/xml/features/disallow-doctype-decl"
|
||||||
|
parser._reader.setFeature(feature, True)
|
||||||
|
except AttributeError:
|
||||||
|
# For CPython / expat parser.
|
||||||
|
# Anything not handled ends up here and entities aren't expanded.
|
||||||
|
parser.DefaultHandler = lambda x: None
|
||||||
|
# Expects an integer return; zero means failure -> expat.ExpatError.
|
||||||
|
parser.ExternalEntityRefHandler = lambda *x: 1
|
||||||
|
if hasattr(xml_input, 'read'):
|
||||||
|
parser.ParseFile(xml_input)
|
||||||
|
elif isgenerator(xml_input):
|
||||||
|
for chunk in xml_input:
|
||||||
|
parser.Parse(chunk, False)
|
||||||
|
parser.Parse(b'', True)
|
||||||
|
else:
|
||||||
|
parser.Parse(xml_input, True)
|
||||||
|
return handler.item
|
||||||
|
|
||||||
|
|
||||||
|
def _process_namespace(name, namespaces, ns_sep=':', attr_prefix='@'):
|
||||||
|
if not namespaces:
|
||||||
|
return name
|
||||||
|
try:
|
||||||
|
ns, name = name.rsplit(ns_sep, 1)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
ns_res = namespaces.get(ns.strip(attr_prefix))
|
||||||
|
name = '{}{}{}{}'.format(
|
||||||
|
attr_prefix if ns.startswith(attr_prefix) else '',
|
||||||
|
ns_res, ns_sep, name) if ns_res else name
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def _emit(key, value, content_handler,
|
||||||
|
attr_prefix='@',
|
||||||
|
cdata_key='#text',
|
||||||
|
depth=0,
|
||||||
|
preprocessor=None,
|
||||||
|
pretty=False,
|
||||||
|
newl='\n',
|
||||||
|
indent='\t',
|
||||||
|
namespace_separator=':',
|
||||||
|
namespaces=None,
|
||||||
|
full_document=True,
|
||||||
|
expand_iter=None):
|
||||||
|
key = _process_namespace(key, namespaces, namespace_separator, attr_prefix)
|
||||||
|
if preprocessor is not None:
|
||||||
|
result = preprocessor(key, value)
|
||||||
|
if result is None:
|
||||||
|
return
|
||||||
|
key, value = result
|
||||||
|
if (not hasattr(value, '__iter__')
|
||||||
|
or isinstance(value, _basestring)
|
||||||
|
or isinstance(value, dict)):
|
||||||
|
value = [value]
|
||||||
|
for index, v in enumerate(value):
|
||||||
|
if full_document and depth == 0 and index > 0:
|
||||||
|
raise ValueError('document with multiple roots')
|
||||||
|
if v is None:
|
||||||
|
v = _dict()
|
||||||
|
elif isinstance(v, bool):
|
||||||
|
if v:
|
||||||
|
v = _unicode('true')
|
||||||
|
else:
|
||||||
|
v = _unicode('false')
|
||||||
|
elif not isinstance(v, dict):
|
||||||
|
if expand_iter and hasattr(v, '__iter__') and not isinstance(v, _basestring):
|
||||||
|
v = _dict(((expand_iter, v),))
|
||||||
|
else:
|
||||||
|
v = _unicode(v)
|
||||||
|
if isinstance(v, _basestring):
|
||||||
|
v = _dict(((cdata_key, v),))
|
||||||
|
cdata = None
|
||||||
|
attrs = _dict()
|
||||||
|
children = []
|
||||||
|
for ik, iv in v.items():
|
||||||
|
if ik == cdata_key:
|
||||||
|
cdata = iv
|
||||||
|
continue
|
||||||
|
if ik.startswith(attr_prefix):
|
||||||
|
ik = _process_namespace(ik, namespaces, namespace_separator,
|
||||||
|
attr_prefix)
|
||||||
|
if ik == '@xmlns' and isinstance(iv, dict):
|
||||||
|
for k, v in iv.items():
|
||||||
|
attr = 'xmlns{}'.format(':{}'.format(k) if k else '')
|
||||||
|
attrs[attr] = _unicode(v)
|
||||||
|
continue
|
||||||
|
if not isinstance(iv, _unicode):
|
||||||
|
iv = _unicode(iv)
|
||||||
|
attrs[ik[len(attr_prefix):]] = iv
|
||||||
|
continue
|
||||||
|
children.append((ik, iv))
|
||||||
|
if type(indent) is int:
|
||||||
|
indent = ' ' * indent
|
||||||
|
if pretty:
|
||||||
|
content_handler.ignorableWhitespace(depth * indent)
|
||||||
|
content_handler.startElement(key, AttributesImpl(attrs))
|
||||||
|
if pretty and children:
|
||||||
|
content_handler.ignorableWhitespace(newl)
|
||||||
|
for child_key, child_value in children:
|
||||||
|
_emit(child_key, child_value, content_handler,
|
||||||
|
attr_prefix, cdata_key, depth+1, preprocessor,
|
||||||
|
pretty, newl, indent, namespaces=namespaces,
|
||||||
|
namespace_separator=namespace_separator,
|
||||||
|
expand_iter=expand_iter)
|
||||||
|
if cdata is not None:
|
||||||
|
content_handler.characters(cdata)
|
||||||
|
if pretty and children:
|
||||||
|
content_handler.ignorableWhitespace(depth * indent)
|
||||||
|
content_handler.endElement(key)
|
||||||
|
if pretty and depth:
|
||||||
|
content_handler.ignorableWhitespace(newl)
|
||||||
|
|
||||||
|
|
||||||
|
def unparse(input_dict, output=None, encoding='utf-8', full_document=True,
|
||||||
|
short_empty_elements=False,
|
||||||
|
**kwargs):
|
||||||
|
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
|
||||||
|
|
||||||
|
The resulting XML document is returned as a string, but if `output` (a
|
||||||
|
file-like object) is specified, it is written there instead.
|
||||||
|
|
||||||
|
Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
|
||||||
|
as XML node attributes, whereas keys equal to `cdata_key`
|
||||||
|
(default=`'#text'`) are treated as character data.
|
||||||
|
|
||||||
|
The `pretty` parameter (default=`False`) enables pretty-printing. In this
|
||||||
|
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
|
||||||
|
can be customized with the `newl` and `indent` parameters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if full_document and len(input_dict) != 1:
|
||||||
|
raise ValueError('Document must have exactly one root.')
|
||||||
|
must_return = False
|
||||||
|
if output is None:
|
||||||
|
output = StringIO()
|
||||||
|
must_return = True
|
||||||
|
if short_empty_elements:
|
||||||
|
content_handler = XMLGenerator(output, encoding, True)
|
||||||
|
else:
|
||||||
|
content_handler = XMLGenerator(output, encoding)
|
||||||
|
if full_document:
|
||||||
|
content_handler.startDocument()
|
||||||
|
for key, value in input_dict.items():
|
||||||
|
_emit(key, value, content_handler, full_document=full_document,
|
||||||
|
**kwargs)
|
||||||
|
if full_document:
|
||||||
|
content_handler.endDocument()
|
||||||
|
if must_return:
|
||||||
|
value = output.getvalue()
|
||||||
|
try: # pragma no cover
|
||||||
|
value = value.decode(encoding)
|
||||||
|
except AttributeError: # pragma no cover
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': # pragma: no cover
|
||||||
|
import sys
|
||||||
|
import marshal
|
||||||
|
try:
|
||||||
|
stdin = sys.stdin.buffer
|
||||||
|
stdout = sys.stdout.buffer
|
||||||
|
except AttributeError:
|
||||||
|
stdin = sys.stdin
|
||||||
|
stdout = sys.stdout
|
||||||
|
|
||||||
|
(item_depth,) = sys.argv[1:]
|
||||||
|
item_depth = int(item_depth)
|
||||||
|
|
||||||
|
def handle_item(path, item):
|
||||||
|
marshal.dump((path, item), stdout)
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
root = parse(stdin,
|
||||||
|
item_depth=item_depth,
|
||||||
|
item_callback=handle_item,
|
||||||
|
dict_constructor=dict)
|
||||||
|
if item_depth == 0:
|
||||||
|
handle_item([], root)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
@@ -1107,7 +1107,7 @@ def play_video(item, strm=False, force_direct=False, autoplay=False):
|
|||||||
# logger.error('Failed to resolve hostname, fallback to normal dns')
|
# logger.error('Failed to resolve hostname, fallback to normal dns')
|
||||||
from core import support
|
from core import support
|
||||||
# support.dbg()
|
# support.dbg()
|
||||||
if '|' not in mediaurl and item.referer != False:
|
if '|' not in mediaurl and item.referer != False and 'youtube' not in mediaurl:
|
||||||
mediaurl = mediaurl + '|' + urllib.urlencode(headers)
|
mediaurl = mediaurl + '|' + urllib.urlencode(headers)
|
||||||
|
|
||||||
# video information is obtained.
|
# video information is obtained.
|
||||||
@@ -1618,6 +1618,8 @@ def play_torrent(item, xlistitem, mediaurl):
|
|||||||
xbmc.sleep(3000)
|
xbmc.sleep(3000)
|
||||||
xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")")
|
xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def resume_playback(played_time):
|
def resume_playback(played_time):
|
||||||
class ResumePlayback(xbmcgui.WindowXMLDialog):
|
class ResumePlayback(xbmcgui.WindowXMLDialog):
|
||||||
Close = False
|
Close = False
|
||||||
@@ -1651,14 +1653,26 @@ def resume_playback(played_time):
|
|||||||
self.set_values(False)
|
self.set_values(False)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
if played_time and played_time > 30:
|
if played_time and played_time > 30:
|
||||||
Dialog = ResumePlayback('ResumePlayback.xml', config.get_runtime_path(), played_time=played_time)
|
if config.get_setting('resume_menu') == 0:
|
||||||
Dialog.show()
|
Dialog = ResumePlayback('ResumePlayback.xml', config.get_runtime_path(), played_time=played_time)
|
||||||
t = 0
|
Dialog.show()
|
||||||
while not Dialog.is_close() and t < 100:
|
t = 0
|
||||||
t += 1
|
while not Dialog.is_close() and t < 100:
|
||||||
xbmc.sleep(100)
|
t += 1
|
||||||
if not Dialog.Resume: played_time = 0
|
xbmc.sleep(100)
|
||||||
|
if not Dialog.Resume: played_time = 0
|
||||||
|
else:
|
||||||
|
m, s = divmod(played_time, 60)
|
||||||
|
h, m = divmod(m, 60)
|
||||||
|
idx = xbmcgui.Dialog().contextmenu(
|
||||||
|
[
|
||||||
|
xbmc.getLocalizedString(12022).format('%02d:%02d:%02d' % (h, m, s)),
|
||||||
|
xbmc.getLocalizedString(12021)
|
||||||
|
])
|
||||||
|
if idx in [-1, 0]: played_time = 0
|
||||||
|
|
||||||
else: played_time = 0
|
else: played_time = 0
|
||||||
xbmc.sleep(300)
|
xbmc.sleep(300)
|
||||||
return played_time
|
return played_time
|
||||||
|
|||||||
@@ -617,7 +617,10 @@ def set_content(content_type, silent=False, custom=False):
|
|||||||
values.append(r['addonid'])
|
values.append(r['addonid'])
|
||||||
if not custom:
|
if not custom:
|
||||||
if content_type == 'movie':
|
if content_type == 'movie':
|
||||||
seleccion = values.index('metadata.themoviedb.org')
|
if PY3:
|
||||||
|
seleccion = values.index('metadata.themoviedb.org.python')
|
||||||
|
else:
|
||||||
|
seleccion = values.index('metadata.themoviedb.org')
|
||||||
else:
|
else:
|
||||||
seleccion = values.index('metadata.tvshows.themoviedb.org.python')
|
seleccion = values.index('metadata.tvshows.themoviedb.org.python')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -199,6 +199,18 @@ msgctxt "#30046"
|
|||||||
msgid "Resume from start"
|
msgid "Resume from start"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30047"
|
||||||
|
msgid "Show resume from How:"
|
||||||
|
msgstr "Mostra riprendi da come:"
|
||||||
|
|
||||||
|
msgctxt "#30048"
|
||||||
|
msgid "Custom window"
|
||||||
|
msgstr "Finestra personalizzata"
|
||||||
|
|
||||||
|
msgctxt "#30049"
|
||||||
|
msgid "Skin window"
|
||||||
|
msgstr "Finestra della skin"
|
||||||
|
|
||||||
msgctxt "#30050"
|
msgctxt "#30050"
|
||||||
msgid "Server connection error"
|
msgid "Server connection error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -199,6 +199,18 @@ msgctxt "#30046"
|
|||||||
msgid "Resume from start"
|
msgid "Resume from start"
|
||||||
msgstr "Riprendi dall'inizio"
|
msgstr "Riprendi dall'inizio"
|
||||||
|
|
||||||
|
msgctxt "#30047"
|
||||||
|
msgid "Show resume from How:"
|
||||||
|
msgstr "Mostra riprendi da come:"
|
||||||
|
|
||||||
|
msgctxt "#30048"
|
||||||
|
msgid "Custom window"
|
||||||
|
msgstr "Finestra personalizzata"
|
||||||
|
|
||||||
|
msgctxt "#30049"
|
||||||
|
msgid "Skin window"
|
||||||
|
msgstr "Finestra della skin"
|
||||||
|
|
||||||
msgctxt "#30050"
|
msgctxt "#30050"
|
||||||
msgid "Server connection error"
|
msgid "Server connection error"
|
||||||
msgstr "Errore connessione server"
|
msgstr "Errore connessione server"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<setting id="servers_favorites" visible="true" type="action" label="60551" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiAic2VydmVyc19mYXZvcml0ZXMiLA0KICAgICJjaGFubmVsIjogInNldHRpbmciDQp9==)"/>
|
<setting id="servers_favorites" visible="true" type="action" label="60551" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiAic2VydmVyc19mYXZvcml0ZXMiLA0KICAgICJjaGFubmVsIjogInNldHRpbmciDQp9==)"/>
|
||||||
<setting id="servers_blacklist" visible="true" type="action" label="60550" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiAic2VydmVyc19ibGFja2xpc3QiLA0KICAgICJjaGFubmVsIjogInNldHRpbmciDQp9==)"/>
|
<setting id="servers_blacklist" visible="true" type="action" label="60550" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiAic2VydmVyc19ibGFja2xpc3QiLA0KICAgICJjaGFubmVsIjogInNldHRpbmciDQp9==)"/>
|
||||||
<setting id="window_type" type="select" lvalues="60622|60623" label="60621" default="0"/>
|
<setting id="window_type" type="select" lvalues="60622|60623" label="60621" default="0"/>
|
||||||
|
<setting id="resume_menu" type="select" lvalues="30048|30049" label="30047" default="0"/>
|
||||||
<!-- <setting id="hide_servers" type="bool" label="70747" default="false" visible="eq(-1,true)" subsetting="true"/> -->
|
<!-- <setting id="hide_servers" type="bool" label="70747" default="false" visible="eq(-1,true)" subsetting="true"/> -->
|
||||||
<setting id="checklinks" type="bool" label="30020" default="false"/>
|
<setting id="checklinks" type="bool" label="30020" default="false"/>
|
||||||
<setting id="checklinks_number" type="slider" option="int" range="5,5,20" label="30021" default="5" visible="eq(-1,true)" subsetting="true"/>
|
<setting id="checklinks_number" type="slider" option="int" range="5,5,20" label="30021" default="5" visible="eq(-1,true)" subsetting="true"/>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"ignore_urls": [],
|
"ignore_urls": [],
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"pattern": "dood(?:stream)?.[^/]+/+(?:e|d)/([a-z0-9]+)",
|
"pattern": "(do*d(?:stream)?.[^/]+)/+(?:e|d)/([a-z0-9]+)",
|
||||||
"url": "https://dood.yt/e/\\1"
|
"url": "https://\\1/e/\\2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+11
-7
@@ -1,15 +1,20 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import time, string, random
|
import time, string, random, sys
|
||||||
from core import httptools, support, servertools
|
from core import httptools, support
|
||||||
from platformcode import logger, config
|
from platformcode import logger, config
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
else:
|
||||||
|
from urllib import urlparse
|
||||||
|
|
||||||
|
|
||||||
def test_video_exists(page_url):
|
def test_video_exists(page_url):
|
||||||
global data
|
global data
|
||||||
logger.debug('page url=', page_url)
|
logger.debug('page url=', page_url)
|
||||||
|
|
||||||
response = httptools.downloadpage(page_url)
|
response = httptools.downloadpage(page_url, cloudscraper=True)
|
||||||
if response.code == 404 or 'dsplayer' not in response.data:
|
if response.code == 404 or 'dsplayer' not in response.data:
|
||||||
return False, config.get_localized_string(70449) % 'DooD Stream'
|
return False, config.get_localized_string(70449) % 'DooD Stream'
|
||||||
else:
|
else:
|
||||||
@@ -22,14 +27,13 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
|
|||||||
logger.debug("URL", page_url)
|
logger.debug("URL", page_url)
|
||||||
|
|
||||||
video_urls = []
|
video_urls = []
|
||||||
host = 'https://' + servertools.get_server_host('doodstream')[0]
|
host = 'https://{}'.format(urlparse(page_url).netloc)
|
||||||
headers = {'User-Agent': httptools.get_user_agent(), 'Referer': page_url}
|
headers = {'User-Agent': httptools.get_user_agent(), 'Referer': host}
|
||||||
support.dbg()
|
|
||||||
|
|
||||||
match = support.match(data, patron=r'''dsplayer\.hotkeys[^']+'([^']+).+?function\s*makePlay.+?return[^?]+([^"]+)''').match
|
match = support.match(data, patron=r'''dsplayer\.hotkeys[^']+'([^']+).+?function\s*makePlay.+?return[^?]+([^"]+)''').match
|
||||||
if match:
|
if match:
|
||||||
url, token = match
|
url, token = match
|
||||||
ret = httptools.downloadpage(host + url, headers=headers).data
|
ret = httptools.downloadpage(host + url, headers=headers, cloudscraper=True).data
|
||||||
video_urls.append(['mp4 [DooD Stream]', '{}{}{}{}|Referer={}'.format(randomize(ret), url, token, int(time.time() * 1000), host)])
|
video_urls.append(['mp4 [DooD Stream]', '{}{}{}{}|Referer={}'.format(randomize(ret), url, token, int(time.time() * 1000), host)])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,5 +20,5 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
|
|||||||
packed = support.match(data, patron=r'(eval\(function\(p.*?)</').match
|
packed = support.match(data, patron=r'(eval\(function\(p.*?)</').match
|
||||||
if packed:
|
if packed:
|
||||||
data = jsunpack.unpack(packed).replace("\\", "")
|
data = jsunpack.unpack(packed).replace("\\", "")
|
||||||
video_urls = support.get_jwplayer_mediaurl(data, 'filemoon')
|
video_urls = support.get_jwplayer_mediaurl(data, 'filemoon', hls=True)
|
||||||
return video_urls
|
return video_urls
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ignore_urls": [],
|
"ignore_urls": [],
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"pattern": "mixdro?ps?.[^/]+/(?:f|e)/([a-z0-9]+)",
|
"pattern": "mixdro?o?ps?.[^/]+/(?:f|e)/([a-z0-9]+)",
|
||||||
"url": "https://mixdrop.co/e/\\1"
|
"url": "https://mixdrop.co/e/\\1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"active": true,
|
||||||
|
"find_videos": {
|
||||||
|
"ignore_urls": [],
|
||||||
|
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"pattern": "ahvsh.com/[a-z]/([\\d\\w]+)",
|
||||||
|
"url": "https://ahvsh.com/e/\\1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"free": true,
|
||||||
|
"id": "streamhide",
|
||||||
|
"name": "StreamHide",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"default": false,
|
||||||
|
"enabled": true,
|
||||||
|
"id": "black_list",
|
||||||
|
"label": "@70708",
|
||||||
|
"type": "bool",
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cloudflare": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from core import httptools, support
|
||||||
|
from core import scrapertools
|
||||||
|
from platformcode import logger, config
|
||||||
|
|
||||||
|
|
||||||
|
def test_video_exists(page_url):
|
||||||
|
logger.debug("(page_url='%s')" % page_url)
|
||||||
|
global data
|
||||||
|
|
||||||
|
data = httptools.downloadpage(page_url).data
|
||||||
|
|
||||||
|
if "File is no longer available" in data:
|
||||||
|
return False, config.get_localized_string(70449) % "StreamHide"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
||||||
|
logger.debug("url=" + page_url)
|
||||||
|
global data
|
||||||
|
|
||||||
|
return support.get_jwplayer_mediaurl(data, 'StreamHide', hls=True)
|
||||||
@@ -1,68 +1,77 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import urllib.parse
|
||||||
|
import ast
|
||||||
|
import xbmc
|
||||||
|
|
||||||
from core import httptools, support, filetools
|
from core import httptools, support, filetools
|
||||||
from platformcode import logger, config
|
from platformcode import logger, config
|
||||||
UA = httptools.random_useragent()
|
from concurrent import futures
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
vttsupport = False if int(xbmc.getInfoLabel('System.BuildVersion').split('.')[0]) < 20 else True
|
||||||
|
|
||||||
|
|
||||||
def test_video_exists(page_url):
|
def test_video_exists(page_url):
|
||||||
global scws_id
|
global iframe
|
||||||
logger.debug('page url=', page_url)
|
global iframeParams
|
||||||
scws_id = ''
|
|
||||||
|
|
||||||
if page_url.isdigit():
|
iframe = support.scrapertools.decodeHtmlentities(support.match(page_url, patron='<iframe [^>]+src="([^"]+)').match)
|
||||||
scws_id = page_url
|
iframeParams = support.match(iframe, patron='window\.masterPlaylistParams\s=\s({.*?})').match
|
||||||
else:
|
|
||||||
page = httptools.downloadpage(page_url)
|
if not iframeParams:
|
||||||
if page.url == page_url: # se non esiste, reindirizza all'ultimo url chiamato esistente
|
return 'StreamingCommunity', 'Prossimamente'
|
||||||
scws_id = support.scrapertools.find_single_match(page.data, r'scws_id[^:]+:(\d+)')
|
|
||||||
else:
|
|
||||||
return 'StreamingCommunity', 'Prossimamente'
|
|
||||||
|
|
||||||
if not scws_id:
|
|
||||||
return False, config.get_localized_string(70449) % 'StreamingCommunityWS'
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
||||||
from time import time
|
urls = list()
|
||||||
from base64 import b64encode
|
subs = list()
|
||||||
from hashlib import md5
|
local_subs = list()
|
||||||
|
|
||||||
global scws_id
|
|
||||||
video_urls = list()
|
video_urls = list()
|
||||||
|
|
||||||
# clientIp = httptools.downloadpage(f'https://scws.work/videos/{scws_id}').json.get('client_ip')
|
scws_id = urlparse(iframe).path.split('/')[-1]
|
||||||
clientIp = httptools.downloadpage('http://ip-api.com/json/').json.get('query')
|
masterPlaylistParams = ast.literal_eval(iframeParams)
|
||||||
if clientIp:
|
url = 'https://scws.work/v2/playlist/{}?{}&n=1'.format(scws_id, urllib.parse.urlencode(masterPlaylistParams))
|
||||||
expires = int(time() + 172800)
|
|
||||||
token = b64encode(md5('{}{} Yc8U6r8KjAKAepEA'.format(expires, clientIp).encode('utf-8')).digest()).decode('utf-8').replace('=', '').replace('+', '-').replace('/', '_')
|
|
||||||
url = 'https://scws.work/master/{}?token={}&expires={}&n=1'.format(scws_id, token, expires)
|
|
||||||
if page_url.isdigit():
|
|
||||||
video_urls.append(['m3u8', '{}|User-Agent={}'.format(url, UA)])
|
|
||||||
else:
|
|
||||||
video_urls = compose(url)
|
|
||||||
|
|
||||||
return video_urls
|
info = support.match(url, patron=r'LANGUAGE="([^"]+)",\s*URI="([^"]+)|(http.*?rendition=(\d+)[^\s]+)').matches
|
||||||
|
|
||||||
def compose(url):
|
if info:
|
||||||
subs = []
|
for lang, sub, url, res in info:
|
||||||
video_urls = []
|
|
||||||
info = support.match(url, patron=r'LANGUAGE="([^"]+)",\s*URI="([^"]+)|RESOLUTION=\d+x(\d+).*?(http[^"\s]+)', headers={'User-Agent':UA}).matches
|
|
||||||
if info and not logger.testMode: # ai test non piace questa parte
|
|
||||||
for lang, sub, res, url in info:
|
|
||||||
if sub:
|
if sub:
|
||||||
while True:
|
|
||||||
match = support.match(sub, patron=r'(http[^\s\n]+)').match
|
|
||||||
if match:
|
|
||||||
sub = httptools.downloadpage(match).data
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if lang == 'auto': lang = 'ita-forced'
|
if lang == 'auto': lang = 'ita-forced'
|
||||||
s = config.get_temp_file(lang +'.srt')
|
subs.append([lang, sub])
|
||||||
subs.append(s)
|
elif not 'token=&' in url:
|
||||||
filetools.write(s, support.vttToSrt(sub))
|
urls.append([res, url])
|
||||||
elif url:
|
|
||||||
video_urls.append(['m3u8 [{}]'.format(res), '{}|User-Agent={}'.format(url, UA), 0, subs])
|
if subs:
|
||||||
|
local_subs = subs_downloader(subs)
|
||||||
|
video_urls = [['m3u8 [{}]'.format(res), url, 0, local_subs] for res, url in urls]
|
||||||
|
else:
|
||||||
|
video_urls = [['m3u8 [{}]'.format(res), url] for res, url in urls]
|
||||||
else:
|
else:
|
||||||
video_urls.append(['m3u8', '{}|User-Agent={}'.format(url, UA)])
|
video_urls = [['hls', url]]
|
||||||
|
|
||||||
return video_urls
|
return video_urls
|
||||||
|
|
||||||
|
|
||||||
|
def subs_downloader(subs):
|
||||||
|
def subs_downloader_thread(n, s):
|
||||||
|
lang, url = s
|
||||||
|
match = support.match(url, patron=r'(http[^\s\n]+)').match
|
||||||
|
if match:
|
||||||
|
data = httptools.downloadpage(match).data
|
||||||
|
if lang == 'auto': lang = 'ita-forced'
|
||||||
|
sub = config.get_temp_file('{}.{}'.format(lang, 'vtt' if vttsupport else 'str'))
|
||||||
|
filetools.write(sub, data if vttsupport else support.vttToSrt(data))
|
||||||
|
return n, sub
|
||||||
|
|
||||||
|
local_subs = list()
|
||||||
|
|
||||||
|
with futures.ThreadPoolExecutor() as executor:
|
||||||
|
itlist = [executor.submit(subs_downloader_thread, n, s) for n, s in enumerate(subs)]
|
||||||
|
for res in futures.as_completed(itlist):
|
||||||
|
if res.result():
|
||||||
|
local_subs.append(res.result())
|
||||||
|
|
||||||
|
return [s[1] for s in sorted(local_subs, key=lambda n: n[0])]
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
"find_videos": {
|
"find_videos": {
|
||||||
"ignore_urls": [],
|
"ignore_urls": [],
|
||||||
"patterns": [{
|
"patterns": [{
|
||||||
"pattern": "(?:streamsb|sblanh|sbembed|sbembed1|sbplay1|sbplay|pelistop|tubesb|playersb|embedsb|watchsb|streamas|sbfast|sbfull|viewsb|sbvideo|cloudemb|sbplay2|japopav|javplaya|ssbstream|sbthe|sbspeed|sbanh|sblongvu|sbchill|sbhight|sbbrisk)\\.\\w{2,5}/(?:embed-|d/|e/)?([A-z0-9]+)",
|
"pattern": "(?:streamsb|sblanh|sblona|sbembed|sbembed1|sbplay1|sbplay|pelistop|tubesb|playersb|embedsb|watchsb|streamas|sbfast|sbfull|viewsb|sbvideo|cloudemb|sbplay2|japopav|javplaya|ssbstream|sbthe|sbspeed|sbanh|sblongvu|sbchill|sbhight|sbbrisk)\\.\\w{2,5}/(?:embed-|d/|e/)?([A-z0-9]+)",
|
||||||
"url": "https://streamas.cloud/e/\\1.html"
|
"url": "https://streamas.cloud/d/\\1.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "(?:cloudemb.com)/([A-z0-9]+)",
|
"pattern": "(?:cloudemb.com)/([A-z0-9]+)",
|
||||||
"url": "https://streamas.cloud/e/\\1.html"
|
"url": "https://streamas.cloud/d/\\1.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "animeworld.biz/(?:embed-|d/|e/)?([A-z0-9]+)",
|
"pattern": "animeworld.biz/(?:embed-|d/|e/)?([A-z0-9]+)",
|
||||||
"url": "https://streamas.cloud/e/\\1.html"
|
"url": "https://streamas.cloud/d/\\1.html"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+64
-36
@@ -1,53 +1,81 @@
|
|||||||
from core import httptools
|
import re
|
||||||
|
from core import httptools, support, scrapertools
|
||||||
from platformcode import config, logger, platformtools
|
from platformcode import config, logger, platformtools
|
||||||
import random, string
|
|
||||||
import codecs
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib.parse as urllib
|
||||||
|
except ImportError:
|
||||||
|
import urllib
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
from concurrent import futures
|
||||||
|
else:
|
||||||
|
from concurrent_py2 import futures
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
|
||||||
|
host = 'https://streamas.cloud'
|
||||||
|
|
||||||
def get_sources(page_url):
|
def get_sources(page_url):
|
||||||
code = page_url.split('/')[-1].split('.html')[0]
|
sources = support.match(page_url, headers={'watchsb': 'sbstream', 'User-Agent': httptools.get_user_agent()}, replace_headers=True, patron=r'download_video([^"]+).*?<span>\s*(\d+)').matches
|
||||||
rand1 = "".join([random.choice(string.ascii_letters) for y in range(12)])
|
if sources:
|
||||||
rand2 = "".join([random.choice(string.ascii_letters) for y in range(12)])
|
sources = {s[1]: s[0].replace('(','').replace(')','').replace("'",'').split(',') for s in sources}
|
||||||
_0x470d0b = '{}||{}||{}||streamsb'.format(rand1, code, rand2)
|
return sources
|
||||||
|
|
||||||
prefix = 'https://streamas.cloud/sources'
|
|
||||||
suffix = '/' + codecs.getencoder('hex')(_0x470d0b.encode())[0].decode()
|
|
||||||
number = config.get_setting('number', server='streamsb')
|
|
||||||
sources = prefix + str(number) + suffix
|
|
||||||
# does not lite other headers different than watchsb and useragent
|
|
||||||
ret = httptools.downloadpage(sources, headers={'watchsb': 'sbstream', 'User-Agent': httptools.get_user_agent()}, replace_headers=True).json
|
|
||||||
if not ret: # probably number changed
|
|
||||||
wait = platformtools.dialog_progress('StreamSB', config.get_localized_string(60293))
|
|
||||||
for number in range(100):
|
|
||||||
if httptools.downloadpage(prefix + str(number) + '/').code == 200:
|
|
||||||
config.set_setting('number', server='streamsb', value=number)
|
|
||||||
sources = prefix + str(number) + suffix
|
|
||||||
# does not lite other headers different than watchsb and useragent
|
|
||||||
ret = httptools.downloadpage(sources,
|
|
||||||
headers={'watchsb': 'sbstream', 'User-Agent': httptools.get_user_agent()},
|
|
||||||
replace_headers=True).json
|
|
||||||
break
|
|
||||||
wait.close()
|
|
||||||
logger.debug(ret)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def test_video_exists(page_url):
|
def test_video_exists(page_url):
|
||||||
global sources
|
global sources
|
||||||
sources = get_sources(page_url)
|
sources = get_sources(page_url)
|
||||||
|
|
||||||
if 'error' in sources:
|
if sources:
|
||||||
return False, config.get_localized_string(70449) % "StreamSB"
|
|
||||||
else:
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
else:
|
||||||
|
return False, config.get_localized_string(70449) % "StreamSB"
|
||||||
|
|
||||||
|
|
||||||
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
||||||
global sources
|
global sources
|
||||||
file = sources['stream_data']['file']
|
video_urls = list()
|
||||||
backup = sources['stream_data']['backup']
|
if sources:
|
||||||
return [["m3u8 [StreamSB]", file], ["m3u8-altern [StreamSB]", backup]]
|
action = config.get_setting('default_action')
|
||||||
|
if action == 0:
|
||||||
|
progress = platformtools.dialog_progress_bg("StreamSB", message="Risoluzione URLs")
|
||||||
|
step = int(100 / len(sources))
|
||||||
|
percent = 0
|
||||||
|
for res, url in sources.items():
|
||||||
|
progress.update(percent, "Risoluzione URL: {}p".format(res))
|
||||||
|
r, u = resolve_url(res, url)
|
||||||
|
percent += step
|
||||||
|
progress.update(percent, "Risoluzione URL: {}p".format(res))
|
||||||
|
video_urls.append(['{} [{}]'.format(u.split('.')[-1], r), u])
|
||||||
|
progress.close()
|
||||||
|
else:
|
||||||
|
res = sorted([* sources])[0 if action == 1 else -1]
|
||||||
|
progress = platformtools.dialog_progress_bg("StreamSB", message="Risoluzione URL: {}p".format(res))
|
||||||
|
url = sources[res]
|
||||||
|
r, u = resolve_url(res, url)
|
||||||
|
progress.close()
|
||||||
|
video_urls.append(['{} [{}]'.format(u.split(',')[-1], r), u])
|
||||||
|
|
||||||
|
return video_urls
|
||||||
|
|
||||||
|
|
||||||
def get_filename(page_url):
|
def get_payloads(data, token):
|
||||||
return get_sources(page_url)['stream_data']['title']
|
# support.dbg()
|
||||||
|
payloads = {'g-recaptcha-response': token}
|
||||||
|
for name, value in support.match(data, patron=r'input type="hidden" name="([^"]+)" value="([^"]+)').matches:
|
||||||
|
payloads[name] = value
|
||||||
|
return payloads
|
||||||
|
|
||||||
|
def resolve_url(res, params):
|
||||||
|
url = ''
|
||||||
|
source_url = '{}/dl?op=download_orig&id={}&mode={}&hash={}'.format(host, params[0], params[1], params[2])
|
||||||
|
data = httptools.downloadpage(source_url).data
|
||||||
|
co = b64encode((host + ':443').encode('utf-8')).decode('utf-8').replace('=', '')
|
||||||
|
token = scrapertools.girc(data, host, co)
|
||||||
|
payload = get_payloads(data, token)
|
||||||
|
if token:
|
||||||
|
url = support.match(source_url, patron=r'href="([^"]+)"\s*class="btn\s*btn-light', post=payload).match
|
||||||
|
return res, url
|
||||||
@@ -21,7 +21,6 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
|
|||||||
logger.debug("(page_url='%s')" % page_url)
|
logger.debug("(page_url='%s')" % page_url)
|
||||||
video_urls = []
|
video_urls = []
|
||||||
|
|
||||||
video_id = scrapertools.find_single_match(page_url, '(?:v=|embed/)([A-z0-9_-]{11})')
|
|
||||||
inputstream = platformtools.install_inputstream()
|
inputstream = platformtools.install_inputstream()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+12
-12
@@ -186,7 +186,7 @@ def peliculas(item, json='', key='', itemlist=[]):
|
|||||||
itlist = filterkey = []
|
itlist = filterkey = []
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
|
|
||||||
if inspect.stack()[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
if inspect.stack(0)[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
||||||
'search'] and not item.filterkey and not item.disable_pagination:
|
'search'] and not item.filterkey and not item.disable_pagination:
|
||||||
Pagination = int(defp) if defp.isdigit() else ''
|
Pagination = int(defp) if defp.isdigit() else ''
|
||||||
else:
|
else:
|
||||||
@@ -243,7 +243,7 @@ def peliculas(item, json='', key='', itemlist=[]):
|
|||||||
# if item.sort:
|
# if item.sort:
|
||||||
# itemlist.sort(key=lambda x: x.title.lower(), reverse=False)
|
# itemlist.sort(key=lambda x: x.title.lower(), reverse=False)
|
||||||
if Pagination and len(itemlist) >= Pagination:
|
if Pagination and len(itemlist) >= Pagination:
|
||||||
if inspect.stack()[1][3] != 'get_newest':
|
if inspect.stack(0)[1][3] != 'get_newest':
|
||||||
item.title = support.typo(config.get_localized_string(30992), 'color kod bold')
|
item.title = support.typo(config.get_localized_string(30992), 'color kod bold')
|
||||||
item.page = pag + 1
|
item.page = pag + 1
|
||||||
item.thumbnail = support.thumb()
|
item.thumbnail = support.thumb()
|
||||||
@@ -279,13 +279,13 @@ def get_seasons(item):
|
|||||||
contentType='season' if show_seasons else 'tvshow',
|
contentType='season' if show_seasons else 'tvshow',
|
||||||
path=extra.path))
|
path=extra.path))
|
||||||
|
|
||||||
if inspect.stack()[2][3] in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
if inspect.stack(0)[2][3] in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
||||||
'get_newest'] or show_seasons == False:
|
'get_newest'] or show_seasons == False:
|
||||||
itlist = []
|
itlist = []
|
||||||
for item in itemlist:
|
for item in itemlist:
|
||||||
itlist = episodios(item)
|
itlist = episodios(item)
|
||||||
itemlist = itlist
|
itemlist = itlist
|
||||||
if inspect.stack()[2][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
if inspect.stack(0)[2][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
||||||
'get_newest'] and defp and not item.disable_pagination:
|
'get_newest'] and defp and not item.disable_pagination:
|
||||||
itemlist = pagination(item, itemlist)
|
itemlist = pagination(item, itemlist)
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ def episodios(item, json='', key='', itemlist=[]):
|
|||||||
ep = 1
|
ep = 1
|
||||||
season = infoLabels['season'] if 'season' in infoLabels else item.contentSeason if item.contentSeason else 1
|
season = infoLabels['season'] if 'season' in infoLabels else item.contentSeason if item.contentSeason else 1
|
||||||
|
|
||||||
if inspect.stack()[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
if inspect.stack(0)[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes',
|
||||||
'search'] and not show_seasons:
|
'search'] and not show_seasons:
|
||||||
Pagination = int(defp) if defp.isdigit() else ''
|
Pagination = int(defp) if defp.isdigit() else ''
|
||||||
else:
|
else:
|
||||||
@@ -374,7 +374,7 @@ def episodios(item, json='', key='', itemlist=[]):
|
|||||||
path=item.path))
|
path=item.path))
|
||||||
|
|
||||||
# if showseason
|
# if showseason
|
||||||
if inspect.stack()[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes', 'get_newest', 'search']:
|
if inspect.stack(0)[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes', 'get_newest', 'search']:
|
||||||
if show_seasons and not item.filterseason:
|
if show_seasons and not item.filterseason:
|
||||||
itm.contentType = 'season'
|
itm.contentType = 'season'
|
||||||
season_list = []
|
season_list = []
|
||||||
@@ -399,15 +399,15 @@ def episodios(item, json='', key='', itemlist=[]):
|
|||||||
support.videolibrary(itemlist, item)
|
support.videolibrary(itemlist, item)
|
||||||
support.download(itemlist, item)
|
support.download(itemlist, item)
|
||||||
|
|
||||||
elif defp and inspect.stack()[1][3] not in ['get_seasons'] and not item.disable_pagination:
|
elif defp and inspect.stack(0)[1][3] not in ['get_seasons'] and not item.disable_pagination:
|
||||||
if Pagination and len(itemlist) >= Pagination:
|
if Pagination and len(itemlist) >= Pagination:
|
||||||
if inspect.stack()[1][3] != 'get_newest':
|
if inspect.stack(0)[1][3] != 'get_newest':
|
||||||
item.title = support.typo(config.get_localized_string(30992), 'color kod bold')
|
item.title = support.typo(config.get_localized_string(30992), 'color kod bold')
|
||||||
item.page = pag + 1
|
item.page = pag + 1
|
||||||
item.thumbnail = support.thumb()
|
item.thumbnail = support.thumb()
|
||||||
itemlist.append(item)
|
itemlist.append(item)
|
||||||
|
|
||||||
if inspect.stack()[1][3] not in ['get_seasons'] and not show_seasons:
|
if inspect.stack(0)[1][3] not in ['get_seasons'] and not show_seasons:
|
||||||
support.videolibrary(itemlist, item)
|
support.videolibrary(itemlist, item)
|
||||||
support.download(itemlist, item)
|
support.download(itemlist, item)
|
||||||
return itemlist
|
return itemlist
|
||||||
@@ -769,7 +769,7 @@ def set_extra_values(item, json, path):
|
|||||||
ret.subtitle = json[key]
|
ret.subtitle = json[key]
|
||||||
|
|
||||||
if not ret.thumb:
|
if not ret.thumb:
|
||||||
if 'get_search_menu' in inspect.stack()[1][3]:
|
if 'get_search_menu' in inspect.stack(0)[1][3]:
|
||||||
ret.thumb = get_thumb('search.png')
|
ret.thumb = get_thumb('search.png')
|
||||||
else:
|
else:
|
||||||
ret.thumb = item.thumbnail
|
ret.thumb = item.thumbnail
|
||||||
@@ -830,7 +830,7 @@ def pagination(item, itemlist=[]):
|
|||||||
encoded_itemlist = []
|
encoded_itemlist = []
|
||||||
for it in itemlist:
|
for it in itemlist:
|
||||||
encoded_itemlist.append(it.tourl())
|
encoded_itemlist.append(it.tourl())
|
||||||
if inspect.stack()[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes', 'search']:
|
if inspect.stack(0)[1][3] not in ['add_tvshow', 'get_episodes', 'update', 'find_episodes', 'search']:
|
||||||
Pagination = int(defp) if defp.isdigit() else ''
|
Pagination = int(defp) if defp.isdigit() else ''
|
||||||
else:
|
else:
|
||||||
Pagination = ''
|
Pagination = ''
|
||||||
@@ -843,7 +843,7 @@ def pagination(item, itemlist=[]):
|
|||||||
itlist.append(item)
|
itlist.append(item)
|
||||||
|
|
||||||
if Pagination and len(itemlist) >= Pagination:
|
if Pagination and len(itemlist) >= Pagination:
|
||||||
if inspect.stack()[1][3] != 'get_newest':
|
if inspect.stack(0)[1][3] != 'get_newest':
|
||||||
itlist.append(
|
itlist.append(
|
||||||
Item(channel=item.channel,
|
Item(channel=item.channel,
|
||||||
action='pagination',
|
action='pagination',
|
||||||
|
|||||||
@@ -416,7 +416,6 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
self.count += 1
|
self.count += 1
|
||||||
|
|
||||||
return channel, valid, other if other else results
|
return channel, valid, other if other else results
|
||||||
self.update(channel, valid, other if other else results)
|
|
||||||
# update_lock.release()
|
# update_lock.release()
|
||||||
|
|
||||||
def makeItem(self, url):
|
def makeItem(self, url):
|
||||||
@@ -440,6 +439,7 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
return it
|
return it
|
||||||
|
|
||||||
def update(self, channel, valid, results):
|
def update(self, channel, valid, results):
|
||||||
|
update_lock.acquire()
|
||||||
self.LOADING.setVisible(False)
|
self.LOADING.setVisible(False)
|
||||||
if self.exit:
|
if self.exit:
|
||||||
return
|
return
|
||||||
@@ -462,7 +462,8 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
for result in valid:
|
for result in valid:
|
||||||
resultsList += result.tourl() + '|'
|
resultsList += result.tourl() + '|'
|
||||||
item.setProperty('items', resultsList)
|
item.setProperty('items', resultsList)
|
||||||
self.channels[0].setProperty('results', str(len(resultsList.split('|')) - 1 ))
|
res = len(resultsList.split('|'))
|
||||||
|
self.channels[0].setProperty('results', str(res - 1 if res > 0 else 0))
|
||||||
|
|
||||||
if self.CHANNELS.getSelectedPosition() == 0:
|
if self.CHANNELS.getSelectedPosition() == 0:
|
||||||
items = []
|
items = []
|
||||||
@@ -487,7 +488,7 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
})
|
})
|
||||||
for result in results:
|
for result in results:
|
||||||
resultsList += result.tourl() + '|'
|
resultsList += result.tourl() + '|'
|
||||||
item.setProperty('items',resultsList)
|
item.setProperty('items', resultsList)
|
||||||
self.results[name] = len(self.results)
|
self.results[name] = len(self.results)
|
||||||
self.channels.append(item)
|
self.channels.append(item)
|
||||||
else:
|
else:
|
||||||
@@ -497,7 +498,8 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
resultsList += result.tourl() + '|'
|
resultsList += result.tourl() + '|'
|
||||||
item.setProperty('items',resultsList)
|
item.setProperty('items',resultsList)
|
||||||
logger.log(self.channels[int(self.results[name])])
|
logger.log(self.channels[int(self.results[name])])
|
||||||
self.channels[int(self.results[name])].setProperty('results', str(len(resultsList.split('|')) - 1))
|
res = len(resultsList.split('|'))
|
||||||
|
self.channels[int(self.results[name])].setProperty('results', str(res - 1 if res > 0 else 0))
|
||||||
pos = self.CHANNELS.getSelectedPosition()
|
pos = self.CHANNELS.getSelectedPosition()
|
||||||
self.CHANNELS.reset()
|
self.CHANNELS.reset()
|
||||||
self.CHANNELS.addItems(self.channels)
|
self.CHANNELS.addItems(self.channels)
|
||||||
@@ -511,6 +513,7 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
if result: items.append(self.makeItem(result))
|
if result: items.append(self.makeItem(result))
|
||||||
self.RESULTS.reset()
|
self.RESULTS.reset()
|
||||||
self.RESULTS.addItems(items)
|
self.RESULTS.addItems(items)
|
||||||
|
update_lock.release()
|
||||||
|
|
||||||
def onInit(self):
|
def onInit(self):
|
||||||
self.time = time.time()
|
self.time = time.time()
|
||||||
@@ -689,6 +692,7 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
self.itemsResult = getattr(self.channel, item.action)(item)
|
self.itemsResult = getattr(self.channel, item.action)(item)
|
||||||
if self.itemsResult and self.itemsResult[0].server:
|
if self.itemsResult and self.itemsResult[0].server:
|
||||||
from platformcode.launcher import findvideos
|
from platformcode.launcher import findvideos
|
||||||
|
busy(False)
|
||||||
findvideos(self.item, self.itemsResult)
|
findvideos(self.item, self.itemsResult)
|
||||||
return
|
return
|
||||||
except:
|
except:
|
||||||
|
|||||||
+5
-5
@@ -3,10 +3,10 @@ rm tests/home/userdata/addon_data/plugin.video.kod/settings_channels/*.json
|
|||||||
rm tests/home/userdata/addon_data/plugin.video.kod/settings_servers/*.json
|
rm tests/home/userdata/addon_data/plugin.video.kod/settings_servers/*.json
|
||||||
rm tests/home/userdata/addon_data/plugin.video.kod/cookies.dat
|
rm tests/home/userdata/addon_data/plugin.video.kod/cookies.dat
|
||||||
rm tests/home/userdata/addon_data/plugin.video.kod/kod_db.sqlite
|
rm tests/home/userdata/addon_data/plugin.video.kod/kod_db.sqlite
|
||||||
python3.9 -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip3.9 install -U sakee
|
pip install -U sakee
|
||||||
pip3.9 install -U html-testRunner
|
pip install -e git+https://github.com/mac12m99/HtmlTestRunner.git@master#egg=html-testRunner
|
||||||
pip3.9 install -U parameterized
|
pip install -U parameterized
|
||||||
export PYTHONPATH=$PWD
|
export PYTHONPATH=$PWD
|
||||||
export KODI_INTERACTIVE=0
|
export KODI_INTERACTIVE=0
|
||||||
export KODI_HOME=$PWD/tests/home
|
export KODI_HOME=$PWD/tests/home
|
||||||
@@ -14,4 +14,4 @@ if (( $# >= 1 ))
|
|||||||
then
|
then
|
||||||
export KOD_TST_CH=$1
|
export KOD_TST_CH=$1
|
||||||
fi
|
fi
|
||||||
python3.9 tests/test_generic.py
|
python tests/test_generic.py
|
||||||
+15
-7
@@ -12,7 +12,7 @@ import random
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
import datetime
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
if 'KOD_TST_CH' not in os.environ:
|
if 'KOD_TST_CH' not in os.environ:
|
||||||
@@ -54,7 +54,7 @@ from core import servertools, httptools
|
|||||||
import channelselector
|
import channelselector
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
logger.DEBUG_ENABLED = False
|
||||||
httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = 10
|
httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = 10
|
||||||
|
|
||||||
outDir = os.path.join(os.getcwd(), 'reports')
|
outDir = os.path.join(os.getcwd(), 'reports')
|
||||||
@@ -66,7 +66,7 @@ validUrlRegex = re.compile(
|
|||||||
r'(?::\d+)?' # optional port
|
r'(?::\d+)?' # optional port
|
||||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||||
|
|
||||||
chBlackList = ['url', 'mediasetplay', 'metalvideo', 'accuradio']
|
chBlackList = ['url', 'mediasetplay', 'metalvideo', 'accuradio', 'cinetecadibologna', 'tunein']
|
||||||
srvBlacklist = ['mega', 'hdmario', 'torrent', 'youtube']
|
srvBlacklist = ['mega', 'hdmario', 'torrent', 'youtube']
|
||||||
chNumRis = {
|
chNumRis = {
|
||||||
'altadefinizione01': {
|
'altadefinizione01': {
|
||||||
@@ -146,13 +146,14 @@ chNumRis = {
|
|||||||
|
|
||||||
|
|
||||||
def wait():
|
def wait():
|
||||||
time.sleep(random.randint(1, 3))
|
pass
|
||||||
|
# time.sleep(random.randint(1, 3))
|
||||||
|
|
||||||
|
|
||||||
servers = []
|
servers = []
|
||||||
channels = []
|
channels = []
|
||||||
|
|
||||||
channel_list = channelselector.filterchannels("all") if 'KOD_TST_CH' not in os.environ else [Item(channel=os.environ['KOD_TST_CH'], action="mainlist")]
|
channel_list = channelselector.filterchannels("all") if 'KOD_TST_CH' not in os.environ else [Item(channel=os.environ['KOD_TST_CH'], action="mainlist")]
|
||||||
|
logger.DEBUG_ENABLED = True
|
||||||
logger.info([c.channel for c in channel_list])
|
logger.info([c.channel for c in channel_list])
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@@ -181,7 +182,9 @@ for chItem in channel_list:
|
|||||||
for it in mainlist:
|
for it in mainlist:
|
||||||
wait()
|
wait()
|
||||||
try:
|
try:
|
||||||
print('preparing ' + ch + ' -> ' + it.title)
|
now = datetime.datetime.now()
|
||||||
|
current_time = now.strftime("%H:%M:%S")
|
||||||
|
print(current_time + 'preparing ' + ch + ' -> ' + it.title)
|
||||||
|
|
||||||
if it.action == 'channel_config':
|
if it.action == 'channel_config':
|
||||||
hasChannelConfig = True
|
hasChannelConfig = True
|
||||||
@@ -197,7 +200,12 @@ for chItem in channel_list:
|
|||||||
|
|
||||||
# if more search action (ex: movie, tvshow), firstcontent need to be changed in every menu
|
# if more search action (ex: movie, tvshow), firstcontent need to be changed in every menu
|
||||||
if itemlist and itemlist[0].action in ('findvideos', 'episodios'):
|
if itemlist and itemlist[0].action in ('findvideos', 'episodios'):
|
||||||
firstContent = re.match('[ \w]*', itemlist[0].fulltitle).group(0)
|
for it2 in itemlist:
|
||||||
|
# some sites refuse to search if the search term is too short
|
||||||
|
title = it2.fulltitle if it2.contentType == 'movie' else it2.contentSerieName
|
||||||
|
if len(title) > 5:
|
||||||
|
firstContent = re.match('[ \w]*', title).group(0)
|
||||||
|
break
|
||||||
|
|
||||||
# some sites might have no link inside, but if all results are without servers, there's something wrong
|
# some sites might have no link inside, but if all results are without servers, there's something wrong
|
||||||
for resIt in itemlist:
|
for resIt in itemlist:
|
||||||
|
|||||||
Reference in New Issue
Block a user