KoD 1.7.3

- fix vari\n\n
This commit is contained in:
marco
2021-11-27 18:08:14 +01:00
parent b0325f61d6
commit 7eebb48b89
60 changed files with 10634 additions and 630 deletions

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ custom_code.json
last_commit.txt
__pycache__/
.vscode/settings.json
bin/
lib/abi

View File

@@ -1,4 +1,4 @@
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.2" provider-name="KoD Team">
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.3" provider-name="KoD Team">
<requires>
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
<import addon="metadata.themoviedb.org"/>
@@ -27,9 +27,7 @@
<screenshot>resources/media/screenshot-2.png</screenshot>
<screenshot>resources/media/screenshot-3.png</screenshot>
</assets>
<news>- corretto il playback in tutti i casi (torrent, autoplay, videoteca, libreria ecc..)
- piccole migliorie prestazionali nella ricerca globale
- fix trailer
<news>- fix vari
</news>
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]

View File

@@ -22,7 +22,7 @@
"filmigratis": "https://filmigratis.org",
"guardaseriecam": "https://guardaserie.cam",
"guardaserieclick": "https://www.guardaserie.builders",
"guardaserieicu": "https://www.youtube.com/watch?v=hV3rZZfkH9Q",
"guardaserieicu": "https://guardaserie.cheap",
"hd4me": "https://hd4me.net",
"ilcorsaronero": "https://ilcorsaronero.link",
"ilgeniodellostreaming": "https://ilgeniodellostreaming.re",
@@ -39,6 +39,7 @@
"serietvsubita": "http://serietvsubita.xyz",
"serietvu": "https://www.serietvu.live",
"streamingcommunity": "https://streamingcommunity.art",
"streamingita": "https://www.streamingita.pro",
"streamtime": "https://t.me/s/StreamTime",
"tantifilm": "https://www.tantifilm.sbs",
"tapmovie": "https://it.tapmovie.net",

View File

@@ -140,6 +140,7 @@ def peliculas(item):
@support.scrape
def episodios(item):
registerOrLogin()
logger.debug(item)
# debug = True
data = item.data

View File

@@ -0,0 +1,11 @@
{
"id": "streamingita",
"name": "StreamingITA",
"language": ["ita"],
"active": true,
"thumbnail": "streamingita.png",
"banner": "streamingita.png",
"categories": ["tvshow", "movie"],
"not_active": ["include_in_newest_peliculas", "include_in_newest_anime", "include_in_newest_series"],
"settings": []
}

74
channels/streamingita.py Normal file
View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Canale per streamingITA
# ------------------------------------------------------------
from core import support
from platformcode import logger, config
host = config.get_channel_url()
@support.menu
def mainlist(item):
film = ['/film']
top = [('Generi', ['/film', 'menu', 'genres']),
('Anno', ['/film', 'menu', 'releases'])]
tvshow = ['/tv']
search = ''
return locals()
def search(item, text):
logger.info('search', text)
item.url = item.url + "/?s=" + text
try:
return support.dooplay_search(item)
except:
import sys
for line in sys.exc_info():
logger.error("%s" % line)
return []
def peliculas(item):
return support.dooplay_peliculas(item, False)
def episodios(item):
itemlist = support.dooplay_get_episodes(item)
return itemlist
def findvideos(item):
itemlist = []
for link in support.dooplay_get_links(item, host):
itemlist.append(
item.clone(action="play", url=link['url']))
# if item.contentType == 'episode':
# linkHead = support.httptools.downloadpage(item.url, only_headers=True).headers['link']
# epId = support.scrapertools.find_single_match(linkHead, r'\?p=([0-9]+)>')
# for link in support.dooplay_get_links(item, host, paramList=[['tv', epId, 1, 'title', 'server']]):
# itemlist.append(
# item.clone(action="play", url=link['url']))
# else:
# for link, quality in support.match(item.url, patron="(" + host + """links/[^"]+).*?class="quality">([^<]+)""").matches:
# srv = support.servertools.find_video_items(data=support.httptools.downloadpage(link).data)
# for s in srv:
# s.quality = quality
# itemlist.extend(srv)
return support.server(item, itemlist=itemlist)
@support.scrape
def menu(item):
action = 'peliculas'
# debug = True
if item.args in ['genres', 'releases']:
patronBlock = r'<nav class="' + item.args + r'">(?P<block>.*?)</nav'
patronMenu= r'<a href="(?P<url>[^"]+)"[^>]*>(?P<title>[^<]+)<'
else:
patronBlock = r'class="main-header">(?P<block>.*?)headitems'
patronMenu = r'(?P<url>' + host + r'quality/[^/]+/\?post_type=movies)">(?P<title>[^<]+)'
return locals()

View File

@@ -253,7 +253,7 @@ def set_channel_info(parameters):
def auto_filter(auto_lang=False):
list_lang = ['ita', 'vos', 'sub-ita']
if config.get_setting("channel_language") == 'auto' or auto_lang == True:
lang = config.get_localized_string(20001)
lang = config.get_language()
else:
lang = config.get_setting("channel_language", default="all")

View File

@@ -284,7 +284,10 @@ class Item(object):
return '\r\t' + self.tostring('\r\t')
def __eq__(self, other):
return self.__dict__ == other.__dict__
if type(other) == Item:
return self.__dict__ == other.__dict__
else:
return False
def set_parent_content(self, parentContent):
"""

29
lib/esprima/__init__.py Normal file
View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
version = '4.0.1'
__version__ = (4, 0, 1)
from .esprima import * # NOQA

105
lib/esprima/__main__.py Normal file
View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals, print_function, division
import sys
from .esprima import parse, tokenize, Error, toDict
from . import version
def main():
import json
import time
import optparse
usage = "usage: %prog [options] [file.js]"
parser = optparse.OptionParser(usage=usage, version=version)
parser.add_option("--comment", dest="comment",
action="store_true", default=False,
help="Gather all line and block comments in an array")
parser.add_option("--attachComment", dest="attachComment",
action="store_true", default=False,
help="Attach comments to nodes")
parser.add_option("--loc", dest="loc", default=False,
action="store_true",
help="Include line-column location info for each syntax node")
parser.add_option("--range", dest="range", default=False,
action="store_true",
help="Include index-based range for each syntax node")
parser.add_option("--raw", dest="raw", default=False,
action="store_true",
help="Display the raw value of literals")
parser.add_option("--tokens", dest="tokens", default=False,
action="store_true",
help="List all tokens in an array")
parser.add_option("--tolerant", dest="tolerant", default=False,
action="store_true",
help="Tolerate errors on a best-effort basis (experimental)")
parser.add_option("--tokenize", dest="tokenize", default=False,
action="store_true",
help="Only tokenize, do not parse.")
parser.add_option("--module", dest="sourceType", default='string',
action="store_const", const='module',
help="Tolerate errors on a best-effort basis (experimental)")
parser.set_defaults(jsx=True, classProperties=True)
opts, args = parser.parse_args()
if len(args) == 1:
with open(args[0], 'rb') as f:
code = f.read().decode('utf-8')
elif sys.stdin.isatty():
parser.print_help()
return 64
else:
code = sys.stdin.read().decode('utf-8')
options = opts.__dict__
do_tokenize = options.pop('tokenize')
t = time.time()
try:
if do_tokenize:
del options['sourceType']
del options['tokens']
del options['raw']
del options['jsx']
res = toDict(tokenize(code, options=options))
else:
res = toDict(parse(code, options=options))
except Error as e:
res = e.toDict()
dt = time.time() - t + 0.000000001
print(json.dumps(res, indent=4))
print()
print('Parsed everything in', round(dt, 5), 'seconds.')
print('Thats %d characters per second' % (len(code) // dt))
return 0
if __name__ == '__main__':
retval = main()
sys.exit(retval)

125
lib/esprima/character.py Normal file
View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
import sys
import unicodedata
from collections import defaultdict
from .compat import uchr, xrange
# https://stackoverflow.com/questions/14245893/efficiently-list-all-characters-in-a-given-unicode-category
U_CATEGORIES = defaultdict(list)
for c in map(uchr, xrange(sys.maxunicode + 1)):
U_CATEGORIES[unicodedata.category(c)].append(c)
UNICODE_LETTER = set(
U_CATEGORIES['Lu'] + U_CATEGORIES['Ll'] +
U_CATEGORIES['Lt'] + U_CATEGORIES['Lm'] +
U_CATEGORIES['Lo'] + U_CATEGORIES['Nl']
)
UNICODE_OTHER_ID_START = set((
# Other_ID_Start
'\u1885', '\u1886', '\u2118', '\u212E', '\u309B', '\u309C',
# New in Unicode 8.0
'\u08B3', '\u0AF9', '\u13F8', '\u9FCD', '\uAB60', '\U00010CC0', '\U000108E0', '\U0002B820',
# New in Unicode 9.0
'\u1C80', '\U000104DB', '\U0001E922',
'\U0001EE00', '\U0001EE06', '\U0001EE0A',
))
UNICODE_OTHER_ID_CONTINUE = set((
# Other_ID_Continue
'\xB7', '\u0387', '\u1369', '\u136A', '\u136B', '\u136C',
'\u136D', '\u136E', '\u136F', '\u1370', '\u1371', '\u19DA',
# New in Unicode 8.0
'\u08E3', '\uA69E', '\U00011730',
# New in Unicode 9.0
'\u08D4', '\u1DFB', '\uA8C5', '\U00011450',
'\U0001EE03', '\U0001EE0B',
))
UNICODE_COMBINING_MARK = set(U_CATEGORIES['Mn'] + U_CATEGORIES['Mc'])
UNICODE_DIGIT = set(U_CATEGORIES['Nd'])
UNICODE_CONNECTOR_PUNCTUATION = set(U_CATEGORIES['Pc'])
IDENTIFIER_START = UNICODE_LETTER.union(UNICODE_OTHER_ID_START).union(set(('$', '_', '\\')))
IDENTIFIER_PART = IDENTIFIER_START.union(UNICODE_COMBINING_MARK).union(UNICODE_DIGIT).union(UNICODE_CONNECTOR_PUNCTUATION).union(set(('\u200D', '\u200C'))).union(UNICODE_OTHER_ID_CONTINUE)
WHITE_SPACE = set((
'\x09', '\x0B', '\x0C', '\x20', '\xA0',
'\u1680', '\u180E', '\u2000', '\u2001', '\u2002',
'\u2003', '\u2004', '\u2005', '\u2006', '\u2007',
'\u2008', '\u2009', '\u200A', '\u202F', '\u205F',
'\u3000', '\uFEFF',
))
LINE_TERMINATOR = set(('\x0A', '\x0D', '\u2028', '\u2029'))
DECIMAL_CONV = dict((c, n) for n, c in enumerate('0123456789'))
OCTAL_CONV = dict((c, n) for n, c in enumerate('01234567'))
HEX_CONV = dict((c, n) for n, c in enumerate('0123456789abcdef'))
for n, c in enumerate('ABCDEF', 10):
HEX_CONV[c] = n
DECIMAL_DIGIT = set(DECIMAL_CONV.keys())
OCTAL_DIGIT = set(OCTAL_CONV.keys())
HEX_DIGIT = set(HEX_CONV.keys())
class Character:
@staticmethod
def fromCodePoint(code):
return uchr(code)
# https://tc39.github.io/ecma262/#sec-white-space
@staticmethod
def isWhiteSpace(ch):
return ch in WHITE_SPACE
# https://tc39.github.io/ecma262/#sec-line-terminators
@staticmethod
def isLineTerminator(ch):
return ch in LINE_TERMINATOR
# https://tc39.github.io/ecma262/#sec-names-and-keywords
@staticmethod
def isIdentifierStart(ch):
return ch in IDENTIFIER_START
@staticmethod
def isIdentifierPart(ch):
return ch in IDENTIFIER_PART
# https://tc39.github.io/ecma262/#sec-literals-numeric-literals
@staticmethod
def isDecimalDigit(ch):
return ch in DECIMAL_DIGIT
@staticmethod
def isHexDigit(ch):
return ch in HEX_DIGIT
@staticmethod
def isOctalDigit(ch):
return ch in OCTAL_DIGIT

View File

@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .objects import Object
from .nodes import Node
from .syntax import Syntax
class Comment(Node):
def __init__(self, type, value, range=None, loc=None):
self.type = type
self.value = value
self.range = range
self.loc = loc
class Entry(Object):
def __init__(self, comment, start):
self.comment = comment
self.start = start
class NodeInfo(Object):
def __init__(self, node, start):
self.node = node
self.start = start
class CommentHandler(object):
def __init__(self):
self.attach = False
self.comments = []
self.stack = []
self.leading = []
self.trailing = []
def insertInnerComments(self, node, metadata):
# innnerComments for properties empty block
# `function a(:/** comments **\/}`
if node.type is Syntax.BlockStatement and not node.body:
innerComments = []
for i, entry in enumerate(self.leading):
if metadata.end.offset >= entry.start:
innerComments.append(entry.comment)
self.leading[i] = None
self.trailing[i] = None
if innerComments:
node.innerComments = innerComments
self.leading = [v for v in self.leading if v is not None]
self.trailing = [v for v in self.trailing if v is not None]
def findTrailingComments(self, metadata):
trailingComments = []
if self.trailing:
for i, entry in enumerate(self.trailing):
if entry.start >= metadata.end.offset:
trailingComments.append(entry.comment)
if trailingComments:
self.trailing = []
return trailingComments
last = self.stack and self.stack[-1]
if last and last.node.trailingComments:
firstComment = last.node.trailingComments[0]
if firstComment and firstComment.range[0] >= metadata.end.offset:
trailingComments = last.node.trailingComments
del last.node.trailingComments
return trailingComments
def findLeadingComments(self, metadata):
leadingComments = []
target = None
while self.stack:
entry = self.stack and self.stack[-1]
if entry and entry.start >= metadata.start.offset:
target = entry.node
self.stack.pop()
else:
break
if target:
if target.leadingComments:
for i, comment in enumerate(target.leadingComments):
if comment.range[1] <= metadata.start.offset:
leadingComments.append(comment)
target.leadingComments[i] = None
if leadingComments:
target.leadingComments = [v for v in target.leadingComments if v is not None]
if not target.leadingComments:
del target.leadingComments
return leadingComments
for i, entry in enumerate(self.leading):
if entry.start <= metadata.start.offset:
leadingComments.append(entry.comment)
self.leading[i] = None
if leadingComments:
self.leading = [v for v in self.leading if v is not None]
return leadingComments
def visitNode(self, node, metadata):
if node.type is Syntax.Program and node.body:
return
self.insertInnerComments(node, metadata)
trailingComments = self.findTrailingComments(metadata)
leadingComments = self.findLeadingComments(metadata)
if leadingComments:
node.leadingComments = leadingComments
if trailingComments:
node.trailingComments = trailingComments
self.stack.append(NodeInfo(
node=node,
start=metadata.start.offset
))
def visitComment(self, node, metadata):
type = 'Line' if node.type[0] == 'L' else 'Block'
comment = Comment(
type=type,
value=node.value
)
if node.range:
comment.range = node.range
if node.loc:
comment.loc = node.loc
self.comments.append(comment)
if self.attach:
entry = Entry(
comment=Comment(
type=type,
value=node.value,
range=[metadata.start.offset, metadata.end.offset]
),
start=metadata.start.offset
)
if node.loc:
entry.comment.loc = node.loc
node.type = type
self.leading.append(entry)
self.trailing.append(entry)
def visit(self, node, metadata):
if node.type == 'LineComment':
self.visitComment(node, metadata)
elif node.type == 'BlockComment':
self.visitComment(node, metadata)
elif self.attach:
self.visitNode(node, metadata)

72
lib/esprima/compat.py Normal file
View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
import sys
PY3 = sys.version_info >= (3, 0)
if PY3:
# Python 3:
basestring = str
long = int
xrange = range
unicode = str
uchr = chr
def uord(ch):
return ord(ch[0])
else:
basestring = basestring
long = long
xrange = xrange
unicode = unicode
try:
# Python 2 UCS4:
unichr(0x10000)
uchr = unichr
def uord(ch):
return ord(ch[0])
except ValueError:
# Python 2 UCS2:
def uchr(code):
# UTF-16 Encoding
if code <= 0xFFFF:
return unichr(code)
cu1 = ((code - 0x10000) >> 10) + 0xD800
cu2 = ((code - 0x10000) & 1023) + 0xDC00
return unichr(cu1) + unichr(cu2)
def uord(ch):
cp = ord(ch[0])
if cp >= 0xD800 and cp <= 0xDBFF:
second = ord(ch[1])
if second >= 0xDC00 and second <= 0xDFFF:
first = cp
cp = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000
return cp

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
from .compat import unicode
class Error(Exception):
def __init__(self, message, name=None, index=None, lineNumber=None, column=None, description=None):
super(Error, self).__init__(message)
self.message = message
self.name = name
self.index = index
self.lineNumber = lineNumber
self.column = column
# self.description = description
def toString(self):
return '%s: %s' % (self.__class__.__name__, self)
def toDict(self):
d = dict((unicode(k), v) for k, v in self.__dict__.items() if v is not None)
d['message'] = self.toString()
return d
class ErrorHandler:
def __init__(self):
self.errors = []
self.tolerant = False
def recordError(self, error):
self.errors.append(error.toDict())
def tolerate(self, error):
if self.tolerant:
self.recordError(error)
else:
raise error
def createError(self, index, line, col, description):
msg = 'Line %s: %s' % (line, description)
return Error(msg, index=index, lineNumber=line, column=col, description=description)
def throwError(self, index, line, col, description):
raise self.createError(index, line, col, description)
def tolerateError(self, index, line, col, description):
error = self.createError(index, line, col, description)
if self.tolerant:
self.recordError(error)
else:
raise error

125
lib/esprima/esprima.py Normal file
View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .comment_handler import CommentHandler
from .error_handler import Error
from .jsx_parser import JSXParser
from .jsx_syntax import JSXSyntax
from .objects import Array, toDict
from .parser import Parser
from .syntax import Syntax
from .tokenizer import Tokenizer
from .visitor import NodeVisitor
from . import nodes
from . import jsx_nodes
__all__ = ['Syntax', 'JSXSyntax', 'Error', 'NodeVisitor', 'nodes', 'jsx_nodes',
'parse', 'parseModule', 'parseScript', 'tokenize', 'toDict']
def parse(code, options=None, delegate=None, **kwargs):
options = {} if options is None else options.copy()
options.update(kwargs)
# ESNext presset:
if options.get('esnext', False):
options['jsx'] = True
options['classProperties'] = True
commentHandler = None
def proxyDelegate(node, metadata):
if delegate:
new_node = delegate(node, metadata)
if new_node is not None:
node = new_node
if commentHandler:
commentHandler.visit(node, metadata)
return node
parserDelegate = None if delegate is None else proxyDelegate
collectComment = options.get('comment', False)
attachComment = options.get('attachComment', False)
if collectComment or attachComment:
commentHandler = CommentHandler()
commentHandler.attach = attachComment
options['comment'] = True
parserDelegate = proxyDelegate
isModule = options.get('sourceType', 'script') == 'module'
if options.get('jsx', False):
parser = JSXParser(code, options=options, delegate=parserDelegate)
else:
parser = Parser(code, options=options, delegate=parserDelegate)
ast = parser.parseModule() if isModule else parser.parseScript()
if collectComment and commentHandler:
ast.comments = commentHandler.comments
if parser.config.tokens:
ast.tokens = parser.tokens
if parser.config.tolerant:
ast.errors = parser.errorHandler.errors
return ast
def parseModule(code, options=None, delegate=None, **kwargs):
kwargs['sourceType'] = 'module'
return parse(code, options, delegate, **kwargs)
def parseScript(code, options=None, delegate=None, **kwargs):
kwargs['sourceType'] = 'script'
return parse(code, options, delegate, **kwargs)
def tokenize(code, options=None, delegate=None, **kwargs):
options = {} if options is None else options.copy()
options.update(kwargs)
tokenizer = Tokenizer(code, options)
tokens = Array()
try:
while True:
token = tokenizer.getNextToken()
if not token:
break
if delegate:
token = delegate(token)
tokens.append(token)
except Error as e:
tokenizer.errorHandler.tolerate(e)
if tokenizer.errorHandler.tolerant:
tokens.errors = tokenizer.errors()
return tokens

100
lib/esprima/jsx_nodes.py Normal file
View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .nodes import Node
from .jsx_syntax import JSXSyntax
class JSXClosingElement(Node):
def __init__(self, name):
self.type = JSXSyntax.JSXClosingElement
self.name = name
class JSXElement(Node):
def __init__(self, openingElement, children, closingElement):
self.type = JSXSyntax.JSXElement
self.openingElement = openingElement
self.children = children
self.closingElement = closingElement
class JSXEmptyExpression(Node):
def __init__(self):
self.type = JSXSyntax.JSXEmptyExpression
class JSXExpressionContainer(Node):
def __init__(self, expression):
self.type = JSXSyntax.JSXExpressionContainer
self.expression = expression
class JSXIdentifier(Node):
def __init__(self, name):
self.type = JSXSyntax.JSXIdentifier
self.name = name
class JSXMemberExpression(Node):
def __init__(self, object, property):
self.type = JSXSyntax.JSXMemberExpression
self.object = object
self.property = property
class JSXAttribute(Node):
def __init__(self, name, value):
self.type = JSXSyntax.JSXAttribute
self.name = name
self.value = value
class JSXNamespacedName(Node):
def __init__(self, namespace, name):
self.type = JSXSyntax.JSXNamespacedName
self.namespace = namespace
self.name = name
class JSXOpeningElement(Node):
def __init__(self, name, selfClosing, attributes):
self.type = JSXSyntax.JSXOpeningElement
self.name = name
self.selfClosing = selfClosing
self.attributes = attributes
class JSXSpreadAttribute(Node):
def __init__(self, argument):
self.type = JSXSyntax.JSXSpreadAttribute
self.argument = argument
class JSXText(Node):
def __init__(self, value, raw):
self.type = JSXSyntax.JSXText
self.value = value
self.raw = raw

584
lib/esprima/jsx_parser.py Normal file
View File

@@ -0,0 +1,584 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .compat import uchr
from .character import Character
from . import jsx_nodes as JSXNode
from .jsx_syntax import JSXSyntax
from . import nodes as Node
from .parser import Marker, Parser
from .token import Token, TokenName
from .xhtml_entities import XHTMLEntities
class MetaJSXElement(object):
def __init__(self, node=None, opening=None, closing=None, children=None):
self.node = node
self.opening = opening
self.closing = closing
self.children = children
class JSXToken(object):
Identifier = 100
Text = 101
class RawJSXToken(object):
def __init__(self, type=None, value=None, lineNumber=None, lineStart=None, start=None, end=None):
self.type = type
self.value = value
self.lineNumber = lineNumber
self.lineStart = lineStart
self.start = start
self.end = end
TokenName[JSXToken.Identifier] = "JSXIdentifier"
TokenName[JSXToken.Text] = "JSXText"
# Fully qualified element name, e.g. <svg:path> returns "svg:path"
def getQualifiedElementName(elementName):
typ = elementName.type
if typ is JSXSyntax.JSXIdentifier:
id = elementName
qualifiedName = id.name
elif typ is JSXSyntax.JSXNamespacedName:
ns = elementName
qualifiedName = getQualifiedElementName(ns.namespace) + ':' + getQualifiedElementName(ns.name)
elif typ is JSXSyntax.JSXMemberExpression:
expr = elementName
qualifiedName = getQualifiedElementName(expr.object) + '.' + getQualifiedElementName(expr.property)
return qualifiedName
class JSXParser(Parser):
def __init__(self, code, options, delegate):
super(JSXParser, self).__init__(code, options, delegate)
def parsePrimaryExpression(self):
return self.parseJSXRoot() if self.match('<') else super(JSXParser, self).parsePrimaryExpression()
def startJSX(self):
# Unwind the scanner before the lookahead token.
self.scanner.index = self.startMarker.index
self.scanner.lineNumber = self.startMarker.line
self.scanner.lineStart = self.startMarker.index - self.startMarker.column
def finishJSX(self):
# Prime the next lookahead.
self.nextToken()
def reenterJSX(self):
self.startJSX()
self.expectJSX('}')
# Pop the closing '}' added from the lookahead.
if self.config.tokens:
self.tokens.pop()
def createJSXNode(self):
self.collectComments()
return Marker(
index=self.scanner.index,
line=self.scanner.lineNumber,
column=self.scanner.index - self.scanner.lineStart
)
def createJSXChildNode(self):
return Marker(
index=self.scanner.index,
line=self.scanner.lineNumber,
column=self.scanner.index - self.scanner.lineStart
)
def scanXHTMLEntity(self, quote):
result = '&'
valid = True
terminated = False
numeric = False
hex = False
while not self.scanner.eof() and valid and not terminated:
ch = self.scanner.source[self.scanner.index]
if ch == quote:
break
terminated = (ch == ';')
result += ch
self.scanner.index += 1
if not terminated:
length = len(result)
if length == 2:
# e.g. '&#123;'
numeric = (ch == '#')
elif length == 3:
if numeric:
# e.g. '&#x41;'
hex = ch == 'x'
valid = hex or Character.isDecimalDigit(ch)
numeric = numeric and not hex
else:
valid = valid and not (numeric and not Character.isDecimalDigit(ch))
valid = valid and not (hex and not Character.isHexDigit(ch))
if valid and terminated and len(result) > 2:
# e.g. '&#x41;' becomes just '#x41'
st = result[1:-1]
if numeric and len(st) > 1:
result = uchr(int(st[1:], 10))
elif hex and len(st) > 2:
result = uchr(int(st[2:], 16))
elif not numeric and not hex and st in XHTMLEntities:
result = XHTMLEntities[st]
return result
# Scan the next JSX token. This replaces Scanner#lex when in JSX mode.
def lexJSX(self):
ch = self.scanner.source[self.scanner.index]
# < > / : = { }
if ch in ('<', '>', '/', ':', '=', '{', '}'):
value = self.scanner.source[self.scanner.index]
self.scanner.index += 1
return RawJSXToken(
type=Token.Punctuator,
value=value,
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=self.scanner.index - 1,
end=self.scanner.index
)
# " '
if ch in ('\'', '"'):
start = self.scanner.index
quote = self.scanner.source[self.scanner.index]
self.scanner.index += 1
str = ''
while not self.scanner.eof():
ch = self.scanner.source[self.scanner.index]
self.scanner.index += 1
if ch == quote:
break
elif ch == '&':
str += self.scanXHTMLEntity(quote)
else:
str += ch
return RawJSXToken(
type=Token.StringLiteral,
value=str,
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=start,
end=self.scanner.index
)
# ... or .
if ch == '.':
start = self.scanner.index
if self.scanner.source[start + 1:start + 3] == '..':
value = '...'
self.scanner.index += 3
else:
value = '.'
self.scanner.index += 1
return RawJSXToken(
type=Token.Punctuator,
value=value,
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=start,
end=self.scanner.index
)
# `
if ch == '`':
# Only placeholder, since it will be rescanned as a real assignment expression.
return RawJSXToken(
type=Token.Template,
value='',
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=self.scanner.index,
end=self.scanner.index
)
# Identifier can not contain backslash (char code 92).
if Character.isIdentifierStart(ch) and ch != '\\':
start = self.scanner.index
self.scanner.index += 1
while not self.scanner.eof():
ch = self.scanner.source[self.scanner.index]
if Character.isIdentifierPart(ch) and ch != '\\':
self.scanner.index += 1
elif ch == '-':
# Hyphen (char code 45) can be part of an identifier.
self.scanner.index += 1
else:
break
id = self.scanner.source[start:self.scanner.index]
return RawJSXToken(
type=JSXToken.Identifier,
value=id,
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=start,
end=self.scanner.index
)
return self.scanner.lex()
def nextJSXToken(self):
self.collectComments()
self.startMarker.index = self.scanner.index
self.startMarker.line = self.scanner.lineNumber
self.startMarker.column = self.scanner.index - self.scanner.lineStart
token = self.lexJSX()
self.lastMarker.index = self.scanner.index
self.lastMarker.line = self.scanner.lineNumber
self.lastMarker.column = self.scanner.index - self.scanner.lineStart
if self.config.tokens:
self.tokens.append(self.convertToken(token))
return token
def nextJSXText(self):
self.startMarker.index = self.scanner.index
self.startMarker.line = self.scanner.lineNumber
self.startMarker.column = self.scanner.index - self.scanner.lineStart
start = self.scanner.index
text = ''
while not self.scanner.eof():
ch = self.scanner.source[self.scanner.index]
if ch in ('{', '<'):
break
self.scanner.index += 1
text += ch
if Character.isLineTerminator(ch):
self.scanner.lineNumber += 1
if ch == '\r' and self.scanner.source[self.scanner.index] == '\n':
self.scanner.index += 1
self.scanner.lineStart = self.scanner.index
self.lastMarker.index = self.scanner.index
self.lastMarker.line = self.scanner.lineNumber
self.lastMarker.column = self.scanner.index - self.scanner.lineStart
token = RawJSXToken(
type=JSXToken.Text,
value=text,
lineNumber=self.scanner.lineNumber,
lineStart=self.scanner.lineStart,
start=start,
end=self.scanner.index
)
if text and self.config.tokens:
self.tokens.append(self.convertToken(token))
return token
def peekJSXToken(self):
state = self.scanner.saveState()
self.scanner.scanComments()
next = self.lexJSX()
self.scanner.restoreState(state)
return next
# Expect the next JSX token to match the specified punctuator.
# If not, an exception will be thrown.
def expectJSX(self, value):
token = self.nextJSXToken()
if token.type is not Token.Punctuator or token.value != value:
self.throwUnexpectedToken(token)
# Return True if the next JSX token matches the specified punctuator.
def matchJSX(self, *value):
next = self.peekJSXToken()
return next.type is Token.Punctuator and next.value in value
def parseJSXIdentifier(self):
node = self.createJSXNode()
token = self.nextJSXToken()
if token.type is not JSXToken.Identifier:
self.throwUnexpectedToken(token)
return self.finalize(node, JSXNode.JSXIdentifier(token.value))
def parseJSXElementName(self):
node = self.createJSXNode()
elementName = self.parseJSXIdentifier()
if self.matchJSX(':'):
namespace = elementName
self.expectJSX(':')
name = self.parseJSXIdentifier()
elementName = self.finalize(node, JSXNode.JSXNamespacedName(namespace, name))
elif self.matchJSX('.'):
while self.matchJSX('.'):
object = elementName
self.expectJSX('.')
property = self.parseJSXIdentifier()
elementName = self.finalize(node, JSXNode.JSXMemberExpression(object, property))
return elementName
def parseJSXAttributeName(self):
node = self.createJSXNode()
identifier = self.parseJSXIdentifier()
if self.matchJSX(':'):
namespace = identifier
self.expectJSX(':')
name = self.parseJSXIdentifier()
attributeName = self.finalize(node, JSXNode.JSXNamespacedName(namespace, name))
else:
attributeName = identifier
return attributeName
def parseJSXStringLiteralAttribute(self):
node = self.createJSXNode()
token = self.nextJSXToken()
if token.type is not Token.StringLiteral:
self.throwUnexpectedToken(token)
raw = self.getTokenRaw(token)
return self.finalize(node, Node.Literal(token.value, raw))
def parseJSXExpressionAttribute(self):
node = self.createJSXNode()
self.expectJSX('{')
self.finishJSX()
if self.match('}'):
self.tolerateError('JSX attributes must only be assigned a non-empty expression')
expression = self.parseAssignmentExpression()
self.reenterJSX()
return self.finalize(node, JSXNode.JSXExpressionContainer(expression))
def parseJSXAttributeValue(self):
if self.matchJSX('{'):
return self.parseJSXExpressionAttribute()
if self.matchJSX('<'):
return self.parseJSXElement()
return self.parseJSXStringLiteralAttribute()
def parseJSXNameValueAttribute(self):
node = self.createJSXNode()
name = self.parseJSXAttributeName()
value = None
if self.matchJSX('='):
self.expectJSX('=')
value = self.parseJSXAttributeValue()
return self.finalize(node, JSXNode.JSXAttribute(name, value))
def parseJSXSpreadAttribute(self):
node = self.createJSXNode()
self.expectJSX('{')
self.expectJSX('...')
self.finishJSX()
argument = self.parseAssignmentExpression()
self.reenterJSX()
return self.finalize(node, JSXNode.JSXSpreadAttribute(argument))
def parseJSXAttributes(self):
attributes = []
while not self.matchJSX('/', '>'):
attribute = self.parseJSXSpreadAttribute() if self.matchJSX('{') else self.parseJSXNameValueAttribute()
attributes.append(attribute)
return attributes
def parseJSXOpeningElement(self):
node = self.createJSXNode()
self.expectJSX('<')
name = self.parseJSXElementName()
attributes = self.parseJSXAttributes()
selfClosing = self.matchJSX('/')
if selfClosing:
self.expectJSX('/')
self.expectJSX('>')
return self.finalize(node, JSXNode.JSXOpeningElement(name, selfClosing, attributes))
def parseJSXBoundaryElement(self):
node = self.createJSXNode()
self.expectJSX('<')
if self.matchJSX('/'):
self.expectJSX('/')
elementName = self.parseJSXElementName()
self.expectJSX('>')
return self.finalize(node, JSXNode.JSXClosingElement(elementName))
name = self.parseJSXElementName()
attributes = self.parseJSXAttributes()
selfClosing = self.matchJSX('/')
if selfClosing:
self.expectJSX('/')
self.expectJSX('>')
return self.finalize(node, JSXNode.JSXOpeningElement(name, selfClosing, attributes))
def parseJSXEmptyExpression(self):
node = self.createJSXChildNode()
self.collectComments()
self.lastMarker.index = self.scanner.index
self.lastMarker.line = self.scanner.lineNumber
self.lastMarker.column = self.scanner.index - self.scanner.lineStart
return self.finalize(node, JSXNode.JSXEmptyExpression())
def parseJSXExpressionContainer(self):
node = self.createJSXNode()
self.expectJSX('{')
if self.matchJSX('}'):
expression = self.parseJSXEmptyExpression()
self.expectJSX('}')
else:
self.finishJSX()
expression = self.parseAssignmentExpression()
self.reenterJSX()
return self.finalize(node, JSXNode.JSXExpressionContainer(expression))
def parseJSXChildren(self):
children = []
while not self.scanner.eof():
node = self.createJSXChildNode()
token = self.nextJSXText()
if token.start < token.end:
raw = self.getTokenRaw(token)
child = self.finalize(node, JSXNode.JSXText(token.value, raw))
children.append(child)
if self.scanner.source[self.scanner.index] == '{':
container = self.parseJSXExpressionContainer()
children.append(container)
else:
break
return children
def parseComplexJSXElement(self, el):
stack = []
while not self.scanner.eof():
el.children.extend(self.parseJSXChildren())
node = self.createJSXChildNode()
element = self.parseJSXBoundaryElement()
if element.type is JSXSyntax.JSXOpeningElement:
opening = element
if opening.selfClosing:
child = self.finalize(node, JSXNode.JSXElement(opening, [], None))
el.children.append(child)
else:
stack.append(el)
el = MetaJSXElement(
node=node,
opening=opening,
closing=None,
children=[],
)
if element.type is JSXSyntax.JSXClosingElement:
el.closing = element
open = getQualifiedElementName(el.opening.name)
close = getQualifiedElementName(el.closing.name)
if open != close:
self.tolerateError('Expected corresponding JSX closing tag for %0', open)
if stack:
child = self.finalize(el.node, JSXNode.JSXElement(el.opening, el.children, el.closing))
el = stack[-1]
el.children.append(child)
stack.pop()
else:
break
return el
def parseJSXElement(self):
node = self.createJSXNode()
opening = self.parseJSXOpeningElement()
children = []
closing = None
if not opening.selfClosing:
el = self.parseComplexJSXElement(MetaJSXElement(
node=node,
opening=opening,
closing=closing,
children=children
))
children = el.children
closing = el.closing
return self.finalize(node, JSXNode.JSXElement(opening, children, closing))
def parseJSXRoot(self):
# Pop the opening '<' added from the lookahead.
if self.config.tokens:
self.tokens.pop()
self.startJSX()
element = self.parseJSXElement()
self.finishJSX()
return element
def isStartOfExpression(self):
return super(JSXParser, self).isStartOfExpression() or self.match('<')

38
lib/esprima/jsx_syntax.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
class JSXSyntax:
JSXAttribute = "JSXAttribute"
JSXClosingElement = "JSXClosingElement"
JSXElement = "JSXElement"
JSXEmptyExpression = "JSXEmptyExpression"
JSXExpressionContainer = "JSXExpressionContainer"
JSXIdentifier = "JSXIdentifier"
JSXMemberExpression = "JSXMemberExpression"
JSXNamespacedName = "JSXNamespacedName"
JSXOpeningElement = "JSXOpeningElement"
JSXSpreadAttribute = "JSXSpreadAttribute"
JSXText = "JSXText"

90
lib/esprima/messages.py Normal file
View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
# Error messages should be identical to V8.
class Messages:
ObjectPatternAsRestParameter = "Unexpected token {"
BadImportCallArity = "Unexpected token"
BadGetterArity = "Getter must not have any formal parameters"
BadSetterArity = "Setter must have exactly one formal parameter"
BadSetterRestParameter = "Setter function argument must not be a rest parameter"
ConstructorIsAsync = "Class constructor may not be an async method"
ConstructorSpecialMethod = "Class constructor may not be an accessor"
DeclarationMissingInitializer = "Missing initializer in %0 declaration"
DefaultRestParameter = "Unexpected token ="
DefaultRestProperty = "Unexpected token ="
DuplicateBinding = "Duplicate binding %0"
DuplicateConstructor = "A class may only have one constructor"
DuplicateProtoProperty = "Duplicate __proto__ fields are not allowed in object literals"
ForInOfLoopInitializer = "%0 loop variable declaration may not have an initializer"
GeneratorInLegacyContext = "Generator declarations are not allowed in legacy contexts"
IllegalBreak = "Illegal break statement"
IllegalContinue = "Illegal continue statement"
IllegalExportDeclaration = "Unexpected token"
IllegalImportDeclaration = "Unexpected token"
IllegalLanguageModeDirective = "Illegal 'use strict' directive in function with non-simple parameter list"
IllegalReturn = "Illegal return statement"
InvalidEscapedReservedWord = "Keyword must not contain escaped characters"
InvalidHexEscapeSequence = "Invalid hexadecimal escape sequence"
InvalidLHSInAssignment = "Invalid left-hand side in assignment"
InvalidLHSInForIn = "Invalid left-hand side in for-in"
InvalidLHSInForLoop = "Invalid left-hand side in for-loop"
InvalidModuleSpecifier = "Unexpected token"
InvalidRegExp = "Invalid regular expression"
LetInLexicalBinding = "let is disallowed as a lexically bound name"
MissingFromClause = "Unexpected token"
MultipleDefaultsInSwitch = "More than one default clause in switch statement"
NewlineAfterThrow = "Illegal newline after throw"
NoAsAfterImportNamespace = "Unexpected token"
NoCatchOrFinally = "Missing catch or finally after try"
ParameterAfterRestParameter = "Rest parameter must be last formal parameter"
PropertyAfterRestProperty = "Unexpected token"
Redeclaration = "%0 '%1' has already been declared"
StaticPrototype = "Classes may not have static property named prototype"
StrictCatchVariable = "Catch variable may not be eval or arguments in strict mode"
StrictDelete = "Delete of an unqualified identifier in strict mode."
StrictFunction = "In strict mode code, functions can only be declared at top level or inside a block"
StrictFunctionName = "Function name may not be eval or arguments in strict mode"
StrictLHSAssignment = "Assignment to eval or arguments is not allowed in strict mode"
StrictLHSPostfix = "Postfix increment/decrement may not have eval or arguments operand in strict mode"
StrictLHSPrefix = "Prefix increment/decrement may not have eval or arguments operand in strict mode"
StrictModeWith = "Strict mode code may not include a with statement"
StrictOctalLiteral = "Octal literals are not allowed in strict mode."
StrictParamDupe = "Strict mode function may not have duplicate parameter names"
StrictParamName = "Parameter name eval or arguments is not allowed in strict mode"
StrictReservedWord = "Use of future reserved word in strict mode"
StrictVarName = "Variable name may not be eval or arguments in strict mode"
TemplateOctalLiteral = "Octal literals are not allowed in template strings."
UnexpectedEOS = "Unexpected end of input"
UnexpectedIdentifier = "Unexpected identifier"
UnexpectedNumber = "Unexpected number"
UnexpectedReserved = "Unexpected reserved word"
UnexpectedString = "Unexpected string"
UnexpectedTemplate = "Unexpected quasi %0"
UnexpectedToken = "Unexpected token %0"
UnexpectedTokenIllegal = "Unexpected token ILLEGAL"
UnknownLabel = "Undefined label '%0'"
UnterminatedRegExp = "Invalid regular expression: missing /"

620
lib/esprima/nodes.py Normal file
View File

@@ -0,0 +1,620 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .objects import Object
from .syntax import Syntax
from .scanner import RegExp
class Node(Object):
def __dir__(self):
return list(self.__dict__.keys())
def __iter__(self):
return self.__iter__
def keys(self):
return self.__dict__.keys()
def items(self):
return self.__dict__.items()
class ArrayExpression(Node):
def __init__(self, elements):
self.type = Syntax.ArrayExpression
self.elements = elements
class ArrayPattern(Node):
def __init__(self, elements):
self.type = Syntax.ArrayPattern
self.elements = elements
class ArrowFunctionExpression(Node):
def __init__(self, params, body, expression):
self.type = Syntax.ArrowFunctionExpression
self.generator = False
self.isAsync = False
self.params = params
self.body = body
self.expression = expression
class AssignmentExpression(Node):
def __init__(self, operator, left, right):
self.type = Syntax.AssignmentExpression
self.operator = operator
self.left = left
self.right = right
class AssignmentPattern(Node):
def __init__(self, left, right):
self.type = Syntax.AssignmentPattern
self.left = left
self.right = right
class AsyncArrowFunctionExpression(Node):
def __init__(self, params, body, expression):
self.type = Syntax.ArrowFunctionExpression
self.generator = False
self.isAsync = True
self.params = params
self.body = body
self.expression = expression
class AsyncFunctionDeclaration(Node):
def __init__(self, id, params, body):
self.type = Syntax.FunctionDeclaration
self.generator = False
self.expression = False
self.isAsync = True
self.id = id
self.params = params
self.body = body
class AsyncFunctionExpression(Node):
def __init__(self, id, params, body):
self.type = Syntax.FunctionExpression
self.generator = False
self.expression = False
self.isAsync = True
self.id = id
self.params = params
self.body = body
class AwaitExpression(Node):
def __init__(self, argument):
self.type = Syntax.AwaitExpression
self.argument = argument
class BinaryExpression(Node):
def __init__(self, operator, left, right):
self.type = Syntax.LogicalExpression if operator in ('||', '&&') else Syntax.BinaryExpression
self.operator = operator
self.left = left
self.right = right
class BlockStatement(Node):
def __init__(self, body):
self.type = Syntax.BlockStatement
self.body = body
class BreakStatement(Node):
def __init__(self, label):
self.type = Syntax.BreakStatement
self.label = label
class CallExpression(Node):
def __init__(self, callee, args):
self.type = Syntax.CallExpression
self.callee = callee
self.arguments = args
class CatchClause(Node):
def __init__(self, param, body):
self.type = Syntax.CatchClause
self.param = param
self.body = body
class ClassBody(Node):
def __init__(self, body):
self.type = Syntax.ClassBody
self.body = body
class ClassDeclaration(Node):
def __init__(self, id, superClass, body):
self.type = Syntax.ClassDeclaration
self.id = id
self.superClass = superClass
self.body = body
class ClassExpression(Node):
def __init__(self, id, superClass, body):
self.type = Syntax.ClassExpression
self.id = id
self.superClass = superClass
self.body = body
class ComputedMemberExpression(Node):
def __init__(self, object, property):
self.type = Syntax.MemberExpression
self.computed = True
self.object = object
self.property = property
class ConditionalExpression(Node):
def __init__(self, test, consequent, alternate):
self.type = Syntax.ConditionalExpression
self.test = test
self.consequent = consequent
self.alternate = alternate
class ContinueStatement(Node):
def __init__(self, label):
self.type = Syntax.ContinueStatement
self.label = label
class DebuggerStatement(Node):
def __init__(self):
self.type = Syntax.DebuggerStatement
class Directive(Node):
def __init__(self, expression, directive):
self.type = Syntax.ExpressionStatement
self.expression = expression
self.directive = directive
class DoWhileStatement(Node):
def __init__(self, body, test):
self.type = Syntax.DoWhileStatement
self.body = body
self.test = test
class EmptyStatement(Node):
def __init__(self):
self.type = Syntax.EmptyStatement
class ExportAllDeclaration(Node):
def __init__(self, source):
self.type = Syntax.ExportAllDeclaration
self.source = source
class ExportDefaultDeclaration(Node):
def __init__(self, declaration):
self.type = Syntax.ExportDefaultDeclaration
self.declaration = declaration
class ExportNamedDeclaration(Node):
def __init__(self, declaration, specifiers, source):
self.type = Syntax.ExportNamedDeclaration
self.declaration = declaration
self.specifiers = specifiers
self.source = source
class ExportSpecifier(Node):
def __init__(self, local, exported):
self.type = Syntax.ExportSpecifier
self.exported = exported
self.local = local
class ExportDefaultSpecifier(Node):
def __init__(self, local):
self.type = Syntax.ExportDefaultSpecifier
self.local = local
class ExpressionStatement(Node):
def __init__(self, expression):
self.type = Syntax.ExpressionStatement
self.expression = expression
class ForInStatement(Node):
def __init__(self, left, right, body):
self.type = Syntax.ForInStatement
self.each = False
self.left = left
self.right = right
self.body = body
class ForOfStatement(Node):
def __init__(self, left, right, body):
self.type = Syntax.ForOfStatement
self.left = left
self.right = right
self.body = body
class ForStatement(Node):
def __init__(self, init, test, update, body):
self.type = Syntax.ForStatement
self.init = init
self.test = test
self.update = update
self.body = body
class FunctionDeclaration(Node):
def __init__(self, id, params, body, generator):
self.type = Syntax.FunctionDeclaration
self.expression = False
self.isAsync = False
self.id = id
self.params = params
self.body = body
self.generator = generator
class FunctionExpression(Node):
def __init__(self, id, params, body, generator):
self.type = Syntax.FunctionExpression
self.expression = False
self.isAsync = False
self.id = id
self.params = params
self.body = body
self.generator = generator
class Identifier(Node):
def __init__(self, name):
self.type = Syntax.Identifier
self.name = name
class IfStatement(Node):
def __init__(self, test, consequent, alternate):
self.type = Syntax.IfStatement
self.test = test
self.consequent = consequent
self.alternate = alternate
class Import(Node):
def __init__(self):
self.type = Syntax.Import
class ImportDeclaration(Node):
def __init__(self, specifiers, source):
self.type = Syntax.ImportDeclaration
self.specifiers = specifiers
self.source = source
class ImportDefaultSpecifier(Node):
def __init__(self, local):
self.type = Syntax.ImportDefaultSpecifier
self.local = local
class ImportNamespaceSpecifier(Node):
def __init__(self, local):
self.type = Syntax.ImportNamespaceSpecifier
self.local = local
class ImportSpecifier(Node):
def __init__(self, local, imported):
self.type = Syntax.ImportSpecifier
self.local = local
self.imported = imported
class LabeledStatement(Node):
def __init__(self, label, body):
self.type = Syntax.LabeledStatement
self.label = label
self.body = body
class Literal(Node):
def __init__(self, value, raw):
self.type = Syntax.Literal
self.value = value
self.raw = raw
class MetaProperty(Node):
def __init__(self, meta, property):
self.type = Syntax.MetaProperty
self.meta = meta
self.property = property
class MethodDefinition(Node):
def __init__(self, key, computed, value, kind, isStatic):
self.type = Syntax.MethodDefinition
self.key = key
self.computed = computed
self.value = value
self.kind = kind
self.static = isStatic
class FieldDefinition(Node):
def __init__(self, key, computed, value, kind, isStatic):
self.type = Syntax.FieldDefinition
self.key = key
self.computed = computed
self.value = value
self.kind = kind
self.static = isStatic
class Module(Node):
def __init__(self, body):
self.type = Syntax.Program
self.sourceType = 'module'
self.body = body
class NewExpression(Node):
def __init__(self, callee, args):
self.type = Syntax.NewExpression
self.callee = callee
self.arguments = args
class ObjectExpression(Node):
def __init__(self, properties):
self.type = Syntax.ObjectExpression
self.properties = properties
class ObjectPattern(Node):
def __init__(self, properties):
self.type = Syntax.ObjectPattern
self.properties = properties
class Property(Node):
def __init__(self, kind, key, computed, value, method, shorthand):
self.type = Syntax.Property
self.key = key
self.computed = computed
self.value = value
self.kind = kind
self.method = method
self.shorthand = shorthand
class RegexLiteral(Node):
def __init__(self, value, raw, pattern, flags):
self.type = Syntax.Literal
self.value = value
self.raw = raw
self.regex = RegExp(
pattern=pattern,
flags=flags,
)
class RestElement(Node):
def __init__(self, argument):
self.type = Syntax.RestElement
self.argument = argument
class ReturnStatement(Node):
def __init__(self, argument):
self.type = Syntax.ReturnStatement
self.argument = argument
class Script(Node):
def __init__(self, body):
self.type = Syntax.Program
self.sourceType = 'script'
self.body = body
class SequenceExpression(Node):
def __init__(self, expressions):
self.type = Syntax.SequenceExpression
self.expressions = expressions
class SpreadElement(Node):
def __init__(self, argument):
self.type = Syntax.SpreadElement
self.argument = argument
class StaticMemberExpression(Node):
def __init__(self, object, property):
self.type = Syntax.MemberExpression
self.computed = False
self.object = object
self.property = property
class Super(Node):
def __init__(self):
self.type = Syntax.Super
class SwitchCase(Node):
def __init__(self, test, consequent):
self.type = Syntax.SwitchCase
self.test = test
self.consequent = consequent
class SwitchStatement(Node):
def __init__(self, discriminant, cases):
self.type = Syntax.SwitchStatement
self.discriminant = discriminant
self.cases = cases
class TaggedTemplateExpression(Node):
def __init__(self, tag, quasi):
self.type = Syntax.TaggedTemplateExpression
self.tag = tag
self.quasi = quasi
class TemplateElement(Node):
class Value(Object):
def __init__(self, raw, cooked):
self.raw = raw
self.cooked = cooked
def __init__(self, raw, cooked, tail):
self.type = Syntax.TemplateElement
self.value = TemplateElement.Value(raw, cooked)
self.tail = tail
class TemplateLiteral(Node):
def __init__(self, quasis, expressions):
self.type = Syntax.TemplateLiteral
self.quasis = quasis
self.expressions = expressions
class ThisExpression(Node):
def __init__(self):
self.type = Syntax.ThisExpression
class ThrowStatement(Node):
def __init__(self, argument):
self.type = Syntax.ThrowStatement
self.argument = argument
class TryStatement(Node):
def __init__(self, block, handler, finalizer):
self.type = Syntax.TryStatement
self.block = block
self.handler = handler
self.finalizer = finalizer
class UnaryExpression(Node):
def __init__(self, operator, argument):
self.type = Syntax.UnaryExpression
self.prefix = True
self.operator = operator
self.argument = argument
class UpdateExpression(Node):
def __init__(self, operator, argument, prefix):
self.type = Syntax.UpdateExpression
self.operator = operator
self.argument = argument
self.prefix = prefix
class VariableDeclaration(Node):
def __init__(self, declarations, kind):
self.type = Syntax.VariableDeclaration
self.declarations = declarations
self.kind = kind
class VariableDeclarator(Node):
def __init__(self, id, init):
self.type = Syntax.VariableDeclarator
self.id = id
self.init = init
class WhileStatement(Node):
def __init__(self, test, body):
self.type = Syntax.WhileStatement
self.test = test
self.body = body
class WithStatement(Node):
def __init__(self, object, body):
self.type = Syntax.WithStatement
self.object = object
self.body = body
class YieldExpression(Node):
def __init__(self, argument, delegate):
self.type = Syntax.YieldExpression
self.argument = argument
self.delegate = delegate
class ArrowParameterPlaceHolder(Node):
def __init__(self, params):
self.type = Syntax.ArrowParameterPlaceHolder
self.params = params
self.isAsync = False
class AsyncArrowParameterPlaceHolder(Node):
def __init__(self, params):
self.type = Syntax.ArrowParameterPlaceHolder
self.params = params
self.isAsync = True
class BlockComment(Node):
def __init__(self, value):
self.type = Syntax.BlockComment
self.value = value
class LineComment(Node):
def __init__(self, value):
self.type = Syntax.LineComment
self.value = value

46
lib/esprima/objects.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
def toDict(value):
from .visitor import ToDictVisitor
return ToDictVisitor().visit(value)
class Array(list):
pass
class Object(object):
def toDict(self):
from .visitor import ToDictVisitor
return ToDictVisitor().visit(self)
def __repr__(self):
from .visitor import ReprVisitor
return ReprVisitor().visit(self)
def __getattr__(self, name):
return None

3104
lib/esprima/parser.py Normal file

File diff suppressed because it is too large Load Diff

1189
lib/esprima/scanner.py Normal file

File diff suppressed because it is too large Load Diff

100
lib/esprima/syntax.py Normal file
View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
class Syntax:
AssignmentExpression = "AssignmentExpression"
AssignmentPattern = "AssignmentPattern"
ArrayExpression = "ArrayExpression"
ArrayPattern = "ArrayPattern"
ArrowFunctionExpression = "ArrowFunctionExpression"
AwaitExpression = "AwaitExpression"
BlockStatement = "BlockStatement"
BinaryExpression = "BinaryExpression"
BreakStatement = "BreakStatement"
CallExpression = "CallExpression"
CatchClause = "CatchClause"
ClassBody = "ClassBody"
ClassDeclaration = "ClassDeclaration"
ClassExpression = "ClassExpression"
ConditionalExpression = "ConditionalExpression"
ContinueStatement = "ContinueStatement"
DoWhileStatement = "DoWhileStatement"
DebuggerStatement = "DebuggerStatement"
EmptyStatement = "EmptyStatement"
ExportAllDeclaration = "ExportAllDeclaration"
ExportDefaultDeclaration = "ExportDefaultDeclaration"
ExportNamedDeclaration = "ExportNamedDeclaration"
ExportSpecifier = "ExportSpecifier"
ExportDefaultSpecifier = "ExportDefaultSpecifier"
ExpressionStatement = "ExpressionStatement"
ForStatement = "ForStatement"
ForOfStatement = "ForOfStatement"
ForInStatement = "ForInStatement"
FunctionDeclaration = "FunctionDeclaration"
FunctionExpression = "FunctionExpression"
Identifier = "Identifier"
IfStatement = "IfStatement"
Import = "Import"
ImportDeclaration = "ImportDeclaration"
ImportDefaultSpecifier = "ImportDefaultSpecifier"
ImportNamespaceSpecifier = "ImportNamespaceSpecifier"
ImportSpecifier = "ImportSpecifier"
Literal = "Literal"
LabeledStatement = "LabeledStatement"
LogicalExpression = "LogicalExpression"
MemberExpression = "MemberExpression"
MetaProperty = "MetaProperty"
MethodDefinition = "MethodDefinition"
FieldDefinition = "FieldDefinition"
NewExpression = "NewExpression"
ObjectExpression = "ObjectExpression"
ObjectPattern = "ObjectPattern"
Program = "Program"
Property = "Property"
RestElement = "RestElement"
ReturnStatement = "ReturnStatement"
SequenceExpression = "SequenceExpression"
SpreadElement = "SpreadElement"
Super = "Super"
SwitchCase = "SwitchCase"
SwitchStatement = "SwitchStatement"
TaggedTemplateExpression = "TaggedTemplateExpression"
TemplateElement = "TemplateElement"
TemplateLiteral = "TemplateLiteral"
ThisExpression = "ThisExpression"
ThrowStatement = "ThrowStatement"
TryStatement = "TryStatement"
UnaryExpression = "UnaryExpression"
UpdateExpression = "UpdateExpression"
VariableDeclaration = "VariableDeclaration"
VariableDeclarator = "VariableDeclarator"
WhileStatement = "WhileStatement"
WithStatement = "WithStatement"
YieldExpression = "YieldExpression"
ArrowParameterPlaceHolder = "ArrowParameterPlaceHolder"
BlockComment = "BlockComment"
LineComment = "LineComment"

50
lib/esprima/token.py Normal file
View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
class Token:
BooleanLiteral = 1
EOF = 2
Identifier = 3
Keyword = 4
NullLiteral = 5
NumericLiteral = 6
Punctuator = 7
StringLiteral = 8
RegularExpression = 9
Template = 10
TokenName = {}
TokenName[Token.BooleanLiteral] = "Boolean"
TokenName[Token.EOF] = "<end>"
TokenName[Token.Identifier] = "Identifier"
TokenName[Token.Keyword] = "Keyword"
TokenName[Token.NullLiteral] = "Null"
TokenName[Token.NumericLiteral] = "Numeric"
TokenName[Token.Punctuator] = "Punctuator"
TokenName[Token.StringLiteral] = "String"
TokenName[Token.RegularExpression] = "RegularExpression"
TokenName[Token.Template] = "Template"

193
lib/esprima/tokenizer.py Normal file
View File

@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from collections import deque
from .objects import Object
from .error_handler import ErrorHandler
from .scanner import Scanner, SourceLocation, Position, RegExp
from .token import Token, TokenName
class BufferEntry(Object):
def __init__(self, type, value, regex=None, range=None, loc=None):
self.type = type
self.value = value
self.regex = regex
self.range = range
self.loc = loc
class Reader(object):
def __init__(self):
self.values = []
self.curly = self.paren = -1
# A function following one of those tokens is an expression.
def beforeFunctionExpression(self, t):
return t in (
'(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
'return', 'case', 'delete', 'throw', 'void',
# assignment operators
'=', '+=', '-=', '*=', '**=', '/=', '%=', '<<=', '>>=', '>>>=',
'&=', '|=', '^=', ',',
# binary/unary operators
'+', '-', '*', '**', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
'|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
'<=', '<', '>', '!=', '!=='
)
# Determine if forward slash (/) is an operator or part of a regular expression
# https://github.com/mozilla/sweet.js/wiki/design
def isRegexStart(self):
if not self.values:
return True
previous = self.values[-1]
regex = previous is not None
if previous in (
'this',
']',
):
regex = False
elif previous == ')':
keyword = self.values[self.paren - 1]
regex = keyword in ('if', 'while', 'for', 'with')
elif previous == '}':
# Dividing a function by anything makes little sense,
# but we have to check for that.
regex = True
if len(self.values) >= 3 and self.values[self.curly - 3] == 'function':
# Anonymous function, e.g. function(){} /42
check = self.values[self.curly - 4]
regex = not self.beforeFunctionExpression(check) if check else False
elif len(self.values) >= 4 and self.values[self.curly - 4] == 'function':
# Named function, e.g. function f(){} /42/
check = self.values[self.curly - 5]
regex = not self.beforeFunctionExpression(check) if check else True
return regex
def append(self, token):
if token.type in (Token.Punctuator, Token.Keyword):
if token.value == '{':
self.curly = len(self.values)
elif token.value == '(':
self.paren = len(self.values)
self.values.append(token.value)
else:
self.values.append(None)
class Config(Object):
def __init__(self, tolerant=None, comment=None, range=None, loc=None, **options):
self.tolerant = tolerant
self.comment = comment
self.range = range
self.loc = loc
for k, v in options.items():
setattr(self, k, v)
class Tokenizer(object):
def __init__(self, code, options):
self.config = Config(**options)
self.errorHandler = ErrorHandler()
self.errorHandler.tolerant = self.config.tolerant
self.scanner = Scanner(code, self.errorHandler)
self.scanner.trackComment = self.config.comment
self.trackRange = self.config.range
self.trackLoc = self.config.loc
self.buffer = deque()
self.reader = Reader()
def errors(self):
return self.errorHandler.errors
def getNextToken(self):
if not self.buffer:
comments = self.scanner.scanComments()
if self.scanner.trackComment:
for e in comments:
value = self.scanner.source[e.slice[0]:e.slice[1]]
comment = BufferEntry(
type='BlockComment' if e.multiLine else 'LineComment',
value=value
)
if self.trackRange:
comment.range = e.range
if self.trackLoc:
comment.loc = e.loc
self.buffer.append(comment)
if not self.scanner.eof():
if self.trackLoc:
loc = SourceLocation(
start=Position(
line=self.scanner.lineNumber,
column=self.scanner.index - self.scanner.lineStart
),
end=Position(),
)
maybeRegex = self.scanner.source[self.scanner.index] == '/' and self.reader.isRegexStart()
if maybeRegex:
state = self.scanner.saveState()
try:
token = self.scanner.scanRegExp()
except Exception:
self.scanner.restoreState(state)
token = self.scanner.lex()
else:
token = self.scanner.lex()
self.reader.append(token)
entry = BufferEntry(
type=TokenName[token.type],
value=self.scanner.source[token.start:token.end]
)
if self.trackRange:
entry.range = [token.start, token.end]
if self.trackLoc:
loc.end = Position(
line=self.scanner.lineNumber,
column=self.scanner.index - self.scanner.lineStart
)
entry.loc = loc
if token.type is Token.RegularExpression:
entry.regex = RegExp(
pattern=token.pattern,
flags=token.flags,
)
self.buffer.append(entry)
return self.buffer.popleft() if self.buffer else None

40
lib/esprima/utils.py Normal file
View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
import re
from .compat import unicode
def format(messageFormat, *args):
def formatter(m):
formatter.idx += 1
assert formatter.idx < len(args), 'Message reference must be in range'
return unicode(args[formatter.idx])
formatter.idx = -1
return format.re.sub(formatter, messageFormat)
format.re = re.compile(r'%(\d)')

288
lib/esprima/visitor.py Normal file
View File

@@ -0,0 +1,288 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
import json
import types
from collections import deque
from .objects import Object
from .compat import PY3, unicode
class VisitRecursionError(Exception):
pass
class Visited(object):
def __init__(self, result):
if isinstance(result, Visited):
result = result.result
self.result = result
class Visitor(object):
"""
An Object visitor base class that walks the abstract syntax tree and calls a
visitor function for every Object found. This function may return a value
which is forwarded by the `visit` method.
This class is meant to be subclassed, with the subclass adding visitor
methods.
Per default the visitor functions for the nodes are ``'visit_'`` +
class name of the Object. So a `Module` Object visit function would
be `visit_Module`. This behavior can be changed by overriding
the `visit` method. If no visitor function exists for an Object
(return value `None`) the `generic_visit` visitor is used instead.
"""
def __call__(self, obj, metadata):
return self.transform(obj, metadata)
def transform(self, obj, metadata):
"""Transform an Object."""
if isinstance(obj, Object):
method = 'transform_' + obj.__class__.__name__
transformer = getattr(self, method, self.transform_Object)
new_obj = transformer(obj, metadata)
if new_obj is not None and obj is not new_obj:
obj = new_obj
return obj
def transform_Object(self, obj, metadata):
"""Called if no explicit transform function exists for an Object."""
return obj
def generic_visit(self, obj):
return self.visit(self.visit_Object(obj))
def visit(self, obj):
"""Visit an Object."""
if not hasattr(self, 'visitors'):
self._visit_context = {}
self._visit_count = 0
try:
self._visit_count += 1
stack = deque()
stack.append((obj, None))
last_result = None
while stack:
try:
last, visited = stack[-1]
if isinstance(last, types.GeneratorType):
stack.append((last.send(last_result), None))
last_result = None
elif isinstance(last, Visited):
stack.pop()
last_result = last.result
elif isinstance(last, Object):
if last in self._visit_context:
if self._visit_context[last] == self.visit_Object:
visitor = self.visit_RecursionError
else:
visitor = self.visit_Object
else:
method = 'visit_' + last.__class__.__name__
visitor = getattr(self, method, self.visit_Object)
self._visit_context[last] = visitor
stack.pop()
stack.append((visitor(last), last))
else:
method = 'visit_' + last.__class__.__name__
visitor = getattr(self, method, self.visit_Generic)
stack.pop()
stack.append((visitor(last), None))
except StopIteration:
stack.pop()
if visited and visited in self._visit_context:
del self._visit_context[visited]
return last_result
finally:
self._visit_count -= 1
if self._visit_count <= 0:
self._visit_context = {}
def visit_RecursionError(self, obj):
raise VisitRecursionError
def visit_Object(self, obj):
"""Called if no explicit visitor function exists for an Object."""
yield obj.__dict__
yield Visited(obj)
def visit_Generic(self, obj):
"""Called if no explicit visitor function exists for an object."""
yield Visited(obj)
def visit_list(self, obj):
for item in obj:
yield item
yield Visited(obj)
visit_Array = visit_list
def visit_dict(self, obj):
for field, value in list(obj.items()):
if not field.startswith('_'):
yield value
yield Visited(obj)
class NodeVisitor(Visitor):
pass
class ReprVisitor(Visitor):
def visit(self, obj, indent=4, nl="\n", sp="", skip=()):
self.level = 0
if isinstance(indent, int):
indent = " " * indent
self.indent = indent
self.nl = nl
self.sp = sp
self.skip = skip
return super(ReprVisitor, self).visit(obj)
def visit_RecursionError(self, obj):
yield Visited("...")
def visit_Object(self, obj):
value_repr = yield obj.__dict__
yield Visited(value_repr)
def visit_Generic(self, obj):
yield Visited(repr(obj))
def visit_list(self, obj):
indent1 = self.indent * self.level
indent2 = indent1 + self.indent
self.level += 1
try:
items = []
for item in obj:
v = yield item
items.append(v)
if items:
value_repr = "[%s%s%s%s%s%s%s]" % (
self.sp,
self.nl,
indent2,
(",%s%s%s" % (self.nl, self.sp, indent2)).join(items),
self.nl,
indent1,
self.sp,
)
else:
value_repr = "[]"
finally:
self.level -= 1
yield Visited(value_repr)
visit_Array = visit_list
def visit_dict(self, obj):
indent1 = self.indent * self.level
indent2 = indent1 + self.indent
self.level += 1
try:
items = []
for k, item in obj.items():
if item is not None and not k.startswith('_') and k not in self.skip:
v = yield item
items.append("%s: %s" % (k, v))
if items:
value_repr = "{%s%s%s%s%s%s%s}" % (
self.sp,
self.nl,
indent2,
(",%s%s%s" % (self.nl, self.sp, indent2)).join(items),
self.nl,
indent1,
self.sp,
)
else:
value_repr = "{}"
finally:
self.level -= 1
yield Visited(value_repr)
if PY3:
def visit_str(self, obj):
value_repr = json.dumps(obj)
yield Visited(value_repr)
else:
def visit_unicode(self, obj):
value_repr = json.dumps(obj)
yield Visited(value_repr)
def visit_SourceLocation(self, obj):
old_indent, self.indent = self.indent, ""
old_nl, self.nl = self.nl, ""
old_sp, self.sp = self.sp, ""
try:
yield obj
finally:
self.indent = old_indent
self.nl = old_nl
self.sp = old_sp
class ToDictVisitor(Visitor):
map = {
'isAsync': 'async',
'allowAwait': 'await',
}
def visit_RecursionError(self, obj):
yield Visited({
'error': "Infinite recursion detected...",
})
def visit_Object(self, obj):
obj = yield obj.__dict__
yield Visited(obj)
def visit_list(self, obj):
items = []
for item in obj:
v = yield item
items.append(v)
yield Visited(items)
visit_Array = visit_list
def visit_dict(self, obj):
items = []
for k, item in obj.items():
if item is not None and not k.startswith('_'):
v = yield item
k = unicode(k)
items.append((self.map.get(k, k), v))
yield Visited(dict(items))
def visit_SRE_Pattern(self, obj):
yield Visited({})

View File

@@ -0,0 +1,281 @@
# -*- coding: utf-8 -*-
# Copyright JS Foundation and other contributors, https://js.foundation/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
# Generated by generate-xhtml-entities.js. DO NOT MODIFY!
XHTMLEntities = {
'quot': "\u0022",
'amp': "\u0026",
'apos': "\u0027",
'gt': "\u003E",
'nbsp': "\u00A0",
'iexcl': "\u00A1",
'cent': "\u00A2",
'pound': "\u00A3",
'curren': "\u00A4",
'yen': "\u00A5",
'brvbar': "\u00A6",
'sect': "\u00A7",
'uml': "\u00A8",
'copy': "\u00A9",
'ordf': "\u00AA",
'laquo': "\u00AB",
'not': "\u00AC",
'shy': "\u00AD",
'reg': "\u00AE",
'macr': "\u00AF",
'deg': "\u00B0",
'plusmn': "\u00B1",
'sup2': "\u00B2",
'sup3': "\u00B3",
'acute': "\u00B4",
'micro': "\u00B5",
'para': "\u00B6",
'middot': "\u00B7",
'cedil': "\u00B8",
'sup1': "\u00B9",
'ordm': "\u00BA",
'raquo': "\u00BB",
'frac14': "\u00BC",
'frac12': "\u00BD",
'frac34': "\u00BE",
'iquest': "\u00BF",
'Agrave': "\u00C0",
'Aacute': "\u00C1",
'Acirc': "\u00C2",
'Atilde': "\u00C3",
'Auml': "\u00C4",
'Aring': "\u00C5",
'AElig': "\u00C6",
'Ccedil': "\u00C7",
'Egrave': "\u00C8",
'Eacute': "\u00C9",
'Ecirc': "\u00CA",
'Euml': "\u00CB",
'Igrave': "\u00CC",
'Iacute': "\u00CD",
'Icirc': "\u00CE",
'Iuml': "\u00CF",
'ETH': "\u00D0",
'Ntilde': "\u00D1",
'Ograve': "\u00D2",
'Oacute': "\u00D3",
'Ocirc': "\u00D4",
'Otilde': "\u00D5",
'Ouml': "\u00D6",
'times': "\u00D7",
'Oslash': "\u00D8",
'Ugrave': "\u00D9",
'Uacute': "\u00DA",
'Ucirc': "\u00DB",
'Uuml': "\u00DC",
'Yacute': "\u00DD",
'THORN': "\u00DE",
'szlig': "\u00DF",
'agrave': "\u00E0",
'aacute': "\u00E1",
'acirc': "\u00E2",
'atilde': "\u00E3",
'auml': "\u00E4",
'aring': "\u00E5",
'aelig': "\u00E6",
'ccedil': "\u00E7",
'egrave': "\u00E8",
'eacute': "\u00E9",
'ecirc': "\u00EA",
'euml': "\u00EB",
'igrave': "\u00EC",
'iacute': "\u00ED",
'icirc': "\u00EE",
'iuml': "\u00EF",
'eth': "\u00F0",
'ntilde': "\u00F1",
'ograve': "\u00F2",
'oacute': "\u00F3",
'ocirc': "\u00F4",
'otilde': "\u00F5",
'ouml': "\u00F6",
'divide': "\u00F7",
'oslash': "\u00F8",
'ugrave': "\u00F9",
'uacute': "\u00FA",
'ucirc': "\u00FB",
'uuml': "\u00FC",
'yacute': "\u00FD",
'thorn': "\u00FE",
'yuml': "\u00FF",
'OElig': "\u0152",
'oelig': "\u0153",
'Scaron': "\u0160",
'scaron': "\u0161",
'Yuml': "\u0178",
'fnof': "\u0192",
'circ': "\u02C6",
'tilde': "\u02DC",
'Alpha': "\u0391",
'Beta': "\u0392",
'Gamma': "\u0393",
'Delta': "\u0394",
'Epsilon': "\u0395",
'Zeta': "\u0396",
'Eta': "\u0397",
'Theta': "\u0398",
'Iota': "\u0399",
'Kappa': "\u039A",
'Lambda': "\u039B",
'Mu': "\u039C",
'Nu': "\u039D",
'Xi': "\u039E",
'Omicron': "\u039F",
'Pi': "\u03A0",
'Rho': "\u03A1",
'Sigma': "\u03A3",
'Tau': "\u03A4",
'Upsilon': "\u03A5",
'Phi': "\u03A6",
'Chi': "\u03A7",
'Psi': "\u03A8",
'Omega': "\u03A9",
'alpha': "\u03B1",
'beta': "\u03B2",
'gamma': "\u03B3",
'delta': "\u03B4",
'epsilon': "\u03B5",
'zeta': "\u03B6",
'eta': "\u03B7",
'theta': "\u03B8",
'iota': "\u03B9",
'kappa': "\u03BA",
'lambda': "\u03BB",
'mu': "\u03BC",
'nu': "\u03BD",
'xi': "\u03BE",
'omicron': "\u03BF",
'pi': "\u03C0",
'rho': "\u03C1",
'sigmaf': "\u03C2",
'sigma': "\u03C3",
'tau': "\u03C4",
'upsilon': "\u03C5",
'phi': "\u03C6",
'chi': "\u03C7",
'psi': "\u03C8",
'omega': "\u03C9",
'thetasym': "\u03D1",
'upsih': "\u03D2",
'piv': "\u03D6",
'ensp': "\u2002",
'emsp': "\u2003",
'thinsp': "\u2009",
'zwnj': "\u200C",
'zwj': "\u200D",
'lrm': "\u200E",
'rlm': "\u200F",
'ndash': "\u2013",
'mdash': "\u2014",
'lsquo': "\u2018",
'rsquo': "\u2019",
'sbquo': "\u201A",
'ldquo': "\u201C",
'rdquo': "\u201D",
'bdquo': "\u201E",
'dagger': "\u2020",
'Dagger': "\u2021",
'bull': "\u2022",
'hellip': "\u2026",
'permil': "\u2030",
'prime': "\u2032",
'Prime': "\u2033",
'lsaquo': "\u2039",
'rsaquo': "\u203A",
'oline': "\u203E",
'frasl': "\u2044",
'euro': "\u20AC",
'image': "\u2111",
'weierp': "\u2118",
'real': "\u211C",
'trade': "\u2122",
'alefsym': "\u2135",
'larr': "\u2190",
'uarr': "\u2191",
'rarr': "\u2192",
'darr': "\u2193",
'harr': "\u2194",
'crarr': "\u21B5",
'lArr': "\u21D0",
'uArr': "\u21D1",
'rArr': "\u21D2",
'dArr': "\u21D3",
'hArr': "\u21D4",
'forall': "\u2200",
'part': "\u2202",
'exist': "\u2203",
'empty': "\u2205",
'nabla': "\u2207",
'isin': "\u2208",
'notin': "\u2209",
'ni': "\u220B",
'prod': "\u220F",
'sum': "\u2211",
'minus': "\u2212",
'lowast': "\u2217",
'radic': "\u221A",
'prop': "\u221D",
'infin': "\u221E",
'ang': "\u2220",
'and': "\u2227",
'or': "\u2228",
'cap': "\u2229",
'cup': "\u222A",
'int': "\u222B",
'there4': "\u2234",
'sim': "\u223C",
'cong': "\u2245",
'asymp': "\u2248",
'ne': "\u2260",
'equiv': "\u2261",
'le': "\u2264",
'ge': "\u2265",
'sub': "\u2282",
'sup': "\u2283",
'nsub': "\u2284",
'sube': "\u2286",
'supe': "\u2287",
'oplus': "\u2295",
'otimes': "\u2297",
'perp': "\u22A5",
'sdot': "\u22C5",
'lceil': "\u2308",
'rceil': "\u2309",
'lfloor': "\u230A",
'rfloor': "\u230B",
'loz': "\u25CA",
'spades': "\u2660",
'clubs': "\u2663",
'hearts': "\u2665",
'diams': "\u2666",
'lang': "\u27E8",
'rang': "\u27E9",
}

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2017-2018 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
# flake8: noqa
# from . import librecaptcha, extract_strings, user_agents
# from .recaptcha import ReCaptcha
# from .librecaptcha import __version__, get_token, has_gui
# from .user_agents import USER_AGENTS, random_user_agent
# from .__main__ import main

View File

@@ -0,0 +1,280 @@
# Copyright (C) 2017, 2019 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from . import errors
from .errors import UserError, UserExit
from .librecaptcha import get_token, __version__
from .user_agents import random_user_agent
import os
import re
import sys
def get_cmd():
if not sys.argv:
return "librecaptcha"
if sys.argv[0].startswith("./"):
return sys.argv[0]
return os.path.basename(sys.argv[0])
CMD = get_cmd()
USAGE = """\
Usage:
{0} [options] [--] <api-key> <site-url> [<user-agent>]
{0} -h | --help | --version
Arguments:
<api-key> The reCAPTCHA API key to use. This is usually the value of the
"data-sitekey" HTML attribute.
<site-url> The URL of the site that contains the reCAPTCHA challenge.
Should start with http:// or https://. Everything after the
hostname is optional. For example: https://example.com
<user-agent> A user-agent string. The client that will use the obtained
reCAPTCHA token should have this user-agent string. If not
provided, a random user-agent string will be chosen and shown.
Options:
-g --gui Use the GTK 3 GUI (as opposed to the CLI).
--debug Show debugging information while running.
-h --help Show this help message.
--version Show the program version.
""".format(CMD)
def usage(file=sys.stdout):
print(USAGE, end="", file=file)
def usage_error(exit=True):
usage(sys.stderr)
if exit:
sys.exit(1)
class ParsedArgs:
def __init__(self):
self.parse_error = None
self.api_key = None
self.site_url = None
self.user_agent = None
self.gui = False
self.debug = False
self.help = False
self.version = False
class ArgParser:
def __init__(self, args):
self.args = args
self.index = 0
self.positional_index = 0
self.parsed = ParsedArgs()
self.options_done = False
self.end_early = False
@property
def arg(self):
try:
return self.args[self.index]
except IndexError:
return None
@property
def done(self):
return self.end_early or self.index >= len(self.args)
def advance(self):
self.index += 1
def error(self, message):
self.parsed.parse_error = message
self.end_early = True
def parse_long_option(self, arg):
body = arg[len("--"):]
if body == "debug":
self.parsed.debug = True
return
if body == "help":
self.parsed.help = True
self.end_early = True
return
if body == "version":
self.parsed.version = True
self.end_early = True
return
if body == "gui":
self.parsed.gui = True
return
self.error("Unrecognized option: {}".format(arg))
def parse_short_option_char(self, char):
if char == "h":
self.parsed.help = True
self.end_early = True
return
if char == "g":
self.parsed.gui = True
return
self.error("Unrecognized option: -{}".format(char))
def parse_short_option(self, arg):
body = arg[len("-"):]
for char in body:
self.parse_short_option_char(char)
def try_parse_option(self):
arg = self.arg
if arg == "--":
self.options_done = True
return True
if re.match(r"--[^-]", arg):
self.parse_long_option(arg)
return True
if re.match(r"-[^-]", arg):
self.parse_short_option(arg)
return True
return False
def parse_positional(self):
arg = self.arg
if self.positional_index == 0:
self.parsed.api_key = arg
return
if self.positional_index == 1:
self.parsed.site_url = arg
return
if self.positional_index == 2:
self.parsed.user_agent = arg
return
self.error("Unexpected positional argument: {}".format(arg))
def parse_single(self):
if not self.options_done and self.try_parse_option():
return
self.parse_positional()
self.positional_index += 1
def handle_end(self):
if self.end_early:
return
if self.positional_index < 1:
self.error("Missing positional argument: <api-key>")
return
if self.positional_index < 2:
self.error("Missing positional argument: <site-url>")
return
def parse(self):
while not self.done:
self.parse_single()
self.advance()
self.handle_end()
return self.parsed
USER_ERRORS = (
errors.GtkImportError,
errors.SiteUrlParseError,
errors.UnsupportedChallengeError,
)
GOT_TOKEN_MSG = """\
Received token. This token should usually be submitted with the form as the
value of the "g-recaptcha-response" field.
"""
def run(args: ParsedArgs):
random_ua = False
user_agent = args.user_agent
if args.user_agent is None:
random_ua = True
user_agent = random_user_agent()
if args.debug:
print("User-agent string: {}".format(user_agent), file=sys.stderr)
try:
uvtoken = get_token(
api_key=args.api_key,
site_url=args.site_url,
user_agent=user_agent,
gui=args.gui,
debug=args.debug,
)
except USER_ERRORS as e:
raise UserError(str(e)) from e
print(GOT_TOKEN_MSG)
if random_ua:
print("Note: The following user-agent string was used:")
print(user_agent)
print()
print("Token:")
print(uvtoken)
UNEXPECTED_ERR_MSG = """\
An unexpected error occurred. The exception traceback is shown below:
"""
def run_or_exit(args: ParsedArgs):
if args.debug:
return run(args)
try:
return run(args)
except UserExit:
sys.exit(2)
except UserError as e:
if e.show_by_default:
print(e.message, file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print(file=sys.stderr)
sys.exit(2)
except Exception:
print(UNEXPECTED_ERR_MSG, file=sys.stderr)
raise
def main():
args = sys.argv[1:]
parsed = ArgParser(args).parse()
error = parsed.parse_error
if error is not None:
print(error, file=sys.stderr)
print("For usage information, run: {} --help".format(CMD),
file=sys.stderr)
sys.exit(1)
if parsed.help:
usage()
return
if parsed.version:
print(__version__)
return
run_or_exit(parsed)
if __name__ == "__main__":
main()

317
lib/librecaptcha/cli.py Normal file
View File

@@ -0,0 +1,317 @@
# Copyright (C) 2017, 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from .recaptcha import ChallengeGoal, GridDimensions, ImageGridChallenge
from .recaptcha import DynamicSolver, MultiCaptchaSolver, Solver
from .recaptcha import ReCaptcha, Solution
from .typing import List
from PIL import Image, ImageDraw, ImageFont
from threading import Thread
from queue import Queue
import io
import os
import random
import readline # noqa: F401
import subprocess
import sys
import time
TYPEFACES = [
"FreeSans",
"LiberationSans-Regular",
"DejaVuSans",
"Arial",
"arial",
]
def get_font(size: int) -> ImageFont.ImageFont:
for typeface in TYPEFACES:
try:
return ImageFont.truetype(typeface, size=size)
except OSError:
pass
return ImageFont.load_default()
FONT_SIZE = 16
FONT = get_font(FONT_SIZE)
def read_indices(prompt: str, max_index: int) -> List[int]:
while True:
line = input(prompt)
try:
indices = [int(i) - 1 for i in line.split()]
except ValueError:
print("Invalid input.")
continue
if all(0 <= i < max_index for i in indices):
return indices
print("Numbers out of bounds.")
def draw_lines(image: Image.Image, dimensions: GridDimensions):
draw = ImageDraw.Draw(image)
def line(p1, p2):
draw.line([p1, p2], fill=(255, 255, 255), width=2)
for i in range(1, dimensions.rows):
y = image.height * i // dimensions.rows - 1
line((0, y), (image.width, y))
for i in range(1, dimensions.columns):
x = image.width * i // dimensions.columns - 1
line((x, 0), (x, image.height))
def draw_indices(image: Image.Image, dimensions: GridDimensions):
draw = ImageDraw.Draw(image, "RGBA")
for i in range(dimensions.rows * dimensions.columns):
row, column = divmod(i, dimensions.columns)
corner = (
image.width * column // dimensions.columns,
image.height * (row + 1) // dimensions.rows,
)
text_loc = (
corner[0] + round(FONT_SIZE / 2),
corner[1] - round(FONT_SIZE * 1.5),
)
text = str(i + 1)
text_size = FONT.getsize(text)
draw.rectangle([
(text_loc[0] - round(FONT_SIZE / 10), text_loc[1]), (
text_loc[0] + text_size[0] + round(FONT_SIZE / 10),
text_loc[1] + text_size[1] + round(FONT_SIZE / 10),
),
], fill=(0, 0, 0, 128))
draw.text(text_loc, str(i + 1), fill=(255, 255, 255), font=FONT)
def print_temporary(string: str, file=sys.stdout):
end = "" if file.isatty() else "\n"
print(string, file=file, end=end, flush=True)
def clear_temporary(file=sys.stdout):
if not file.isatty():
return
print("\r\x1b[K", file=file, end="", flush=True)
HAS_DISPLAY_CMD = (os.name == "posix")
def run_display_cmd():
return subprocess.Popen(
["display", "-"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def try_display_cmd(image: Image.Image):
global HAS_DISPLAY_CMD
if not HAS_DISPLAY_CMD:
return None
img_buffer = io.BytesIO()
image.save(img_buffer, "png")
img_bytes = img_buffer.getvalue()
try:
proc = run_display_cmd()
except FileNotFoundError:
HAS_DISPLAY_CMD = False
return None
proc.stdin.write(img_bytes)
proc.stdin.close()
return proc
class SolverCli:
def __init__(self, cli: "Cli", solver: Solver):
self.cli = cli
self.solver = solver
self.__image_procs = []
def show_image(self, image):
proc = try_display_cmd(image)
if proc is None:
image.show()
else:
self.__image_procs.append(proc)
def hide_images(self):
for proc in self.__image_procs:
proc.terminate()
self.__image_procs.clear()
def run(self):
self.solver.run()
class DynamicCli(SolverCli):
def __init__(self, cli: "Cli", solver: DynamicSolver):
super().__init__(cli, solver)
self.image_open = False
self.image_queue = Queue()
self.num_pending = 0
def run(self):
challenge = self.solver.get_challenge()
self.cli.handle_challenge(challenge)
image = challenge.image
num_rows = challenge.dimensions.rows
num_columns = challenge.dimensions.columns
num_tiles = challenge.dimensions.count
draw_indices(image, challenge.dimensions)
self.show_image(image)
print("Take a look at the grid of tiles that just appeared. ", end="")
print("({} rows, {} columns)".format(num_rows, num_columns))
print("Which tiles should be selected?")
print("(Top-left is 1; bottom-right is {}.)".format(num_tiles))
indices = read_indices(
"Enter numbers separated by spaces: ",
num_tiles,
)
print()
self.hide_images()
self.select_initial(indices)
self.new_tile_loop()
return self.solver.finish()
def new_tile_loop(self):
while self.num_pending > 0:
print_temporary("Waiting for next image...")
index, image = self.image_queue.get()
clear_temporary()
self.num_pending -= 1
self.show_image(image)
print("Take a look at the image that just appeared.")
accept = input(
"Should this image be selected? [y/N] ",
)[:1].lower() == "y"
print()
self.hide_images()
if accept:
self.select_tile(index)
def select_initial(self, indices):
print_temporary("Selecting images...")
for i, index in enumerate(indices):
if i > 0:
# Avoid sending initial requests simultaneously.
time.sleep(random.uniform(0.5, 1))
self.select_tile(index)
clear_temporary()
def select_tile(self, index: int):
self.num_pending += 1
tile = self.solver.select_tile(index)
def add_to_queue():
self.image_queue.put((index, tile.image))
def target():
time.sleep(tile.delay)
add_to_queue()
if tile.delay > 0:
Thread(target=target, daemon=True).start()
else:
target()
class MultiCaptchaCli(SolverCli):
def __init__(self, cli: "Cli", solver: MultiCaptchaSolver):
super().__init__(cli, solver)
def run(self) -> Solution:
result = self.solver.first_challenge()
while not isinstance(result, Solution):
if not isinstance(result, ImageGridChallenge):
raise TypeError("Unexpected type: {}".format(type(result)))
indices = self.handle_challenge(result)
result = self.solver.select_indices(indices)
return result
def handle_challenge(self, challenge: ImageGridChallenge) -> List[int]:
self.cli.handle_challenge(challenge)
num_rows = challenge.dimensions.rows
num_columns = challenge.dimensions.columns
num_tiles = challenge.dimensions.count
image = challenge.image
draw_lines(image, challenge.dimensions)
draw_indices(image, challenge.dimensions)
self.show_image(image)
print("Take a look at the grid of tiles that just appeared. ", end="")
print("({} rows, {} columns)".format(num_rows, num_columns))
print("Which tiles should be selected?")
print("(Top-left is 1; bottom-right is {}.)".format(num_tiles))
indices = read_indices(
"Enter numbers separated by spaces: ",
num_tiles,
)
print()
self.hide_images()
return indices
class Cli:
def __init__(self, rc: ReCaptcha):
self.rc = rc
self._first = True
def run(self) -> str:
result = self.rc.first_solver()
while not isinstance(result, str):
solution = self.run_solver(result)
result = self.rc.send_solution(solution)
return result
def run_solver(self, solver: Solver) -> Solution:
return {
DynamicSolver: DynamicCli,
MultiCaptchaSolver: MultiCaptchaCli,
}[type(solver)](self, solver).run()
def show_goal(self, goal: ChallengeGoal):
plain = goal.plain
if plain:
print("CHALLENGE OBJECTIVE: {}".format(plain))
return
print("WARNING: Could not determine challenge objective.")
print("Challenge information: {}".format(goal.fallback))
def handle_challenge(self, challenge: ImageGridChallenge):
if not self._first:
print("You must solve another challenge.")
print()
self._first = False
self.show_goal(challenge.goal)

100
lib/librecaptcha/errors.py Normal file
View File

@@ -0,0 +1,100 @@
# Copyright (C) 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
GUI_MISSING_MESSAGE = """\
Error: Could not load the GUI. Is PyGObject installed?
Try (re)installing librecaptcha[gtk] with pip.
For more details, add the --debug option.
"""[:-1]
CHALLENGE_BLOCKED_MESSAGE = """\
Error: Unsupported challenge type: {}
Requests are most likely being blocked; see the previously displayed messages.
"""[:-1]
UNKNOWN_CHALLENGE_MESSAGE = """\
Error: Unsupported challenge type: {}
See the previously displayed messages for more information.
"""[:-1]
class UserError(Exception):
"""A user-facing exception for an expected error condition (e.g., bad
user-supplied data). When librecaptcha is run as a program, exceptions of
this type are shown without a traceback unless --debug is passed.
"""
def __init__(self, message):
super().__init__(message)
@property
def message(self):
return self.args[0]
@property
def show_by_default(self) -> bool:
"""Whether the exception message should be shown to the user by
default. Certain exception types may want to set this to ``False`` if a
detailed message has already been displayed to the user.
"""
return True
class UserExit(UserError):
"""When librecaptcha is run as a program, throwing this exception causes
the program to terminate. The exception message is not shown by default.
"""
def __init__(self, message="Program terminated."):
super().__init__(message)
class GtkImportError(ImportError):
def __str__(self) -> str:
return GUI_MISSING_MESSAGE
class SiteUrlParseError(ValueError):
pass
class UnsupportedChallengeError(Exception):
def __init__(self, challenge_type: str):
self.challenge_type = challenge_type
def __str__(self):
return "Error: Unsupported challenge type: {}".format(
self.challenge_type,
)
class ChallengeBlockedError(UnsupportedChallengeError):
def __str__(self) -> str:
return CHALLENGE_BLOCKED_MESSAGE.format(self.challenge_type)
@property
def show_by_default(self) -> bool:
# A detailed message is already shown in `librecaptcha.get_token()`.
return False
class UnknownChallengeError(UnsupportedChallengeError):
def __str__(self) -> str:
return UNKNOWN_CHALLENGE_MESSAGE.format(self.challenge_type)
@property
def show_by_default(self) -> bool:
# A detailed message is already shown in `librecaptcha.get_token()`.
return False

View File

@@ -0,0 +1,113 @@
# Copyright (C) 2017, 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from .typing import List
import requests
import json
import os
import os.path
import re
import sys
SHOW_WARNINGS = False
def load_javascript(url: str, user_agent: str) -> str:
print("Downloading <{}>...".format(url), file=sys.stderr)
r = requests.get(url, headers={
"User-Agent": user_agent,
})
return r.text
def extract_strings_slimit(javascript: str) -> List[str]:
from slimit.parser import Parser
from slimit import ast
if SHOW_WARNINGS:
parser = Parser()
else:
# File descriptor hackiness to silence warnings
null_fd = os.open(os.devnull, os.O_RDWR)
old_fd = os.dup(2)
try:
os.dup2(null_fd, 2)
parser = Parser()
finally:
os.dup2(old_fd, 2)
os.close(null_fd)
os.close(old_fd)
# Hack to work around https://github.com/rspivak/slimit/issues/52
KEYWORDS = r"(?:catch|delete|return|throw)"
javascript = re.sub(rf"(\.\s*{KEYWORDS})\b", r"\1_", javascript)
javascript = re.sub(rf"\b({KEYWORDS})(\s*:)", r"'\1'\2", javascript)
parsed = parser.parse(javascript)
strings = []
def add_strings(tree, strings):
if tree is None:
return
if not isinstance(tree, (ast.Node, list, tuple)):
raise TypeError("Unexpected item: {!r}".format(tree))
if isinstance(tree, ast.String):
strings.append(tree.value[1:-1])
children = tree
if isinstance(tree, ast.Node):
children = tree.children()
for child in children:
add_strings(child, strings)
add_strings(parsed, strings)
return strings
def extract_strings(javascript: str) -> List[str]:
print("Extracting strings...", file=sys.stderr)
try:
import esprima
except ImportError:
return extract_strings_slimit(javascript)
strings = []
def handle_node(node, *args):
if node.type == "Literal" and isinstance(node.value, str):
strings.append(node.value)
esprima.parseScript(javascript, delegate=handle_node)
return strings
def extract_and_save(
url: str,
path: str,
version: str,
rc_version: str,
user_agent: str,
) -> List[str]:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
print("{}/{}".format(version, rc_version), file=f)
js = load_javascript(url, user_agent)
strings = extract_strings(js)
strings_json = json.dumps(strings)
print('Saving strings to "{}"...'.format(path), file=sys.stderr)
f.write(strings_json)
return strings

787
lib/librecaptcha/gui.py Normal file
View File

@@ -0,0 +1,787 @@
# Copyright (C) 2019 cyclopsian
# Copyright (C) 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from .errors import UserExit, GtkImportError
from .recaptcha import ChallengeGoal, GridDimensions, ImageGridChallenge
from .recaptcha import DynamicSolver, MultiCaptchaSolver, Solver
from .recaptcha import ReCaptcha, Solution
from .typing import Callable, Iterable, List
from PIL import Image
from collections import namedtuple
from typing import Any, Optional, Union
import html
import re
import sys
import threading
try:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
except ImportError as e:
raise GtkImportError from e
def tiles_from_image(
image: Image.Image,
dimensions: GridDimensions,
) -> Iterable[Image.Image]:
tile_width = image.width // dimensions.columns
tile_height = image.height // dimensions.rows
for row in range(0, dimensions.rows):
for column in range(0, dimensions.columns):
left = tile_width * column
top = tile_height * row
right = left + tile_width
bottom = top + tile_height
yield image.crop((left, top, right, bottom))
def image_to_gdk_pixbuf(image: Image.Image):
width, height = image.size
image_bytes = GLib.Bytes(image.tobytes())
has_alpha = (image.mode == "RGBA")
bpp = 4 if has_alpha else 3
return GdkPixbuf.Pixbuf.new_from_bytes(
image_bytes, GdkPixbuf.Colorspace.RGB,
has_alpha, 8, width, height, width * bpp,
)
CSS = """\
grid {
margin-top: 1px;
margin-left: 1px;
}
.challenge-button {
margin: 0;
padding: 0;
border-radius: 0;
box-shadow: none;
-gtk-icon-shadow: none;
border-width: 1px;
margin-top: -1px;
margin-left: -1px;
}
.challenge-check, .challenge-header {
margin-left: 1px;
margin-top: 1px;
color: @theme_selected_fg_color;
background-image: linear-gradient(
@theme_selected_bg_color,
@theme_selected_bg_color
);
}
.challenge-header {
padding: 12px;
}
.challenge-check {
border-radius: 50%;
}
"""
def load_css():
global CSS
if CSS is None:
return
css_provider = Gtk.CssProvider.new()
css_provider.load_from_data(CSS.encode())
CSS = None
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(), css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
Dispatch = Callable[[Any], None]
class DynamicTile:
def __init__(self, dispatch: Dispatch):
self.dispatch = dispatch
self.pres: Optional["DynamicTilePres"] = None
self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self.inner = None
self.inner_size = (0, 0) # (width, height)
@property
def widget(self):
return self.box
def update(self, pres: "DynamicTilePres"):
if pres.same(self.pres):
return
if self.inner is not None:
self.box.remove(self.inner)
self.make_inner(pres.image)
self.box.show_all()
self.pres = pres
def make_inner(self, image: Image.Image):
if image is None:
self.make_spinner()
return
button = Gtk.Button.new()
button.get_style_context().add_class("challenge-button")
button.add(Gtk.Image.new_from_pixbuf(image_to_gdk_pixbuf(image)))
button.connect("clicked", lambda _: self.pres.on_click(self.dispatch))
def on_size_allocate(obj, size):
self.inner_size = (size.width, size.height)
button.connect("size-allocate", on_size_allocate)
self.set_inner(button)
def make_spinner(self):
width, height = (max(n, 32) for n in self.inner_size)
spinner = Gtk.Spinner.new()
spinner.set_size_request(32, 32)
left = (width - 32) // 2
top = (height - 32) // 2
spinner.set_margin_top(top)
spinner.set_margin_start(left)
spinner.set_margin_bottom(height - top - 32)
spinner.set_margin_end(width - left - 32)
self.set_inner(spinner)
spinner.start()
return spinner
def set_inner(self, widget):
self.inner = widget
self.box.add(self.inner)
class MultiCaptchaTile:
def __init__(self, dispatch: Dispatch):
self.dispatch = dispatch
self.pres: Optional["MultiCaptchaTilePres"] = None
self.image = Gtk.Image.new()
self.check = self.make_check()
self.fixed = Gtk.Fixed.new()
self.fixed.put(self.image, 0, 0)
self.fixed.put(self.check, 0, 0)
self.button = Gtk.ToggleButton.new()
self.button.get_style_context().add_class("challenge-button")
self.button.add(self.fixed)
self.toggle_id = None
self.pixbuf = None
self.small_pixbuf = None
self.button.show_all()
@property
def widget(self):
return self.button
def update(self, pres: "MultiCaptchaTilePres"):
if pres.same(self.pres):
return None
if self.pres is None:
def on_toggle(obj):
self._set_active(not obj.get_active())
self.pres.on_click(self.dispatch)
self.toggle_id = self.button.connect("toggled", on_toggle)
if (self.pres and self.pres.image) is not pres.image:
self.pixbuf = image_to_gdk_pixbuf(pres.image)
width = self.pixbuf.get_width()
height = self.pixbuf.get_height()
self.image.set_size_request(width, height)
self.small_pixbuf = self.pixbuf.scale_simple(
width * 0.9, height * 0.9, GdkPixbuf.InterpType.BILINEAR,
)
if pres.selected:
self.check.show()
self.image.set_from_pixbuf(self.small_pixbuf)
else:
self.check.hide()
self.image.set_from_pixbuf(self.pixbuf)
self._set_active(pres.selected)
self.pres = pres
def make_check(self):
check = Gtk.Image.new_from_icon_name(
"object-select-symbolic", Gtk.IconSize.DND,
)
check.set_pixel_size(24)
check.set_no_show_all(True)
check.get_style_context().add_class("challenge-check")
return check
def _set_active(self, selected: bool):
self.button.handler_block(self.toggle_id)
self.button.set_active(selected)
self.button.handler_unblock(self.toggle_id)
class ChallengeTile:
def __init__(self, dispatch: Dispatch):
self.dispatch = dispatch
self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
self.tile = None
@property
def widget(self):
return self.box
def update(self, pres: "TilePres"):
tile_type = {
DynamicTilePres: DynamicTile,
MultiCaptchaTilePres: MultiCaptchaTile,
}[type(pres)]
if type(self.tile) is not tile_type:
if self.tile is not None:
self.box.remove(self.tile.widget)
self.tile = tile_type(self.dispatch)
self.box.add(self.tile.widget)
self.box.show_all()
self.tile.update(pres)
class ImageGridChallengeDialog:
def __init__(self, dispatch: Dispatch):
self.dispatch = dispatch
self.pres: Optional["ImageGridChallengePres"] = None
self.dialog = Gtk.Dialog.new()
self.dialog.set_resizable(False)
self.dialog.set_title("librecaptcha")
self.dialog.set_icon_name("view-refresh-symbolic")
self.verify = self.dialog.add_button("", Gtk.ResponseType.OK)
self.verify.get_style_context().add_class("suggested-action")
def on_click(obj):
# This will get reset in `self.update()`, but it prevents multiple
# clicks from taking effect if the UI temporarily pauses.
self.verify.set_sensitive(False)
self.verify.connect("clicked", on_click)
self.header = Gtk.Label.new("")
self.header.set_xalign(0)
self.header.get_style_context().add_class("challenge-header")
self.content = self.dialog.get_content_area()
self.content.set_spacing(6)
for dir in ["start", "end", "top", "bottom"]:
getattr(self.content, "set_margin_" + dir)(6)
self.content.pack_start(self.header, False, False, 0)
self.grid = None
self.tiles = []
self.dialog.show_all()
def run(self) -> bool:
"""Returns ``True`` on success, or ``False`` if the dialog was closed
without activating the default button.
"""
return self.dialog.run() == Gtk.ResponseType.OK
def destroy(self):
self.dialog.destroy()
def update(self, pres: "ImageGridChallengePres"):
if pres.same(self.pres):
return
dimensions = pres.dimensions
if dimensions != (self.pres and self.pres.dimensions):
if self.grid is not None:
self.content.remove(self.grid)
self.grid = self.make_grid(dimensions)
self.grid.show_all()
self.content.pack_start(self.grid, True, True, 0)
if not pres.same_goal(self.pres):
self.header.set_markup(pres.goal)
if not pres.same_verify_label(self.pres):
self.verify.set_label(pres.verify_label)
self.verify.set_sensitive(pres.is_verify_enabled)
for tile, tile_pres in zip(self.tiles, pres.tiles):
tile.update(tile_pres)
self.pres = pres
def make_grid(self, dimensions: GridDimensions):
grid = Gtk.Grid.new()
self.tiles = []
for row in range(0, dimensions.rows):
for column in range(0, dimensions.columns):
tile = ChallengeTile(self.dispatch)
grid.attach(tile.widget, column, row, 1, 1)
self.tiles.append(tile)
return grid
def format_goal(goal: ChallengeGoal) -> str:
if goal.raw is None:
return goal.fallback
match = re.fullmatch(r"(.*)<strong>(.*)</strong>(.*)", goal.raw)
if not match:
return html.escape(goal.raw)
groups = match.groups()
return '{}<span size="xx-large">{}</span>{}'.format(
*map(html.escape, [
groups[0] and groups[0] + "\n",
groups[1],
groups[2],
]),
)
def format_goal_with_note(goal: ChallengeGoal, note: str) -> str:
return "{}\n{}".format(format_goal(goal), note)
# Messages
Start = namedtuple("Start", [])
FinishChallenge = namedtuple("FinishChallenge", [])
SelectTile = namedtuple("SelectTile", [
"index", # int
])
ReplaceTile = namedtuple("ReplaceTile", [
"index", # int
"image", # Image.Image
])
SetState = namedtuple("SetState", [
"state", # State
])
SetNextChallenge = namedtuple("NextChallenge", [
"challenge", # ImageGridChallenge
])
def gtk_run(f: Callable[[], Any]):
old_excepthook = sys.excepthook
def excepthook(*args, **kwargs):
old_excepthook(*args, **kwargs)
sys.exit(1)
try:
sys.excepthook = excepthook
return f()
finally:
sys.excepthook = old_excepthook
class Gui:
def __init__(self, rc: ReCaptcha):
self.store = Store(self.final_dispatch, rc)
self.view = ImageGridChallengeDialog(self.dispatch)
self.update_pending = False
@property
def dispatch(self) -> Dispatch:
return self.store.dispatch
@property
def state(self) -> Optional["State"]:
return self.store.state
def final_dispatch(self, msg):
self.store.state = reduce_state(self.state, msg)
if not self.update_pending:
self.update_pending = True
GLib.idle_add(self._update)
def _update(self):
self.update_pending = False
pres = self.pres
if pres is not None:
self.view.update(pres)
return False
def run(self) -> str:
load_css()
self.dispatch(Start())
try:
while self.token is None:
if not gtk_run(self.view.run):
raise UserExit
self.dispatch(FinishChallenge())
finally:
self.view.destroy()
while Gtk.events_pending():
Gtk.main_iteration()
return self.token
@property
def pres(self) -> Optional["ImageGridChallengePres"]:
return pres(self.state)
@property
def token(self) -> Optional[str]:
if isinstance(self.state, str):
return self.state
return None
class Store:
state: Optional["State"]
dispatch: Dispatch
def __init__(self, final_dispatch: Dispatch, rc: ReCaptcha):
self.state = None
middleware = WarningMiddleware(self, final_dispatch)
middleware = SolverMiddleware(self, middleware.dispatch, rc)
self.dispatch = middleware.dispatch
def pres(state: "State") -> Optional["ImageGridChallengePres"]:
return {
DynamicState: DynamicPres,
MultiCaptchaState: MultiCaptchaPres,
}.get(type(state), lambda _: None)(state)
def state_from_solver(solver: Solver) -> "SolverState":
return {
DynamicSolver: DynamicState,
MultiCaptchaSolver: MultiCaptchaState,
}[type(solver)].from_new_solver(solver)
# Returns the new state after applying `msg`.
def reduce_state(state: "State", msg) -> "State":
if type(msg) is SetState:
return msg.state
if type(state) in SOLVER_STATE_TYPES:
return state.reduce(msg)
return state
class SolverMiddleware:
def __init__(self, store: Store, next: Dispatch, rc: ReCaptcha):
self.store = store
self.next = next
self.rc = rc
self.solver = None
self._select_tile_lock = threading.Lock()
def dispatch(self, msg):
if type(msg) is Start:
self.solver = self.rc.first_solver()
self.next(SetState(state_from_solver(self.solver)))
elif isinstance(self.solver, DynamicSolver):
self.dispatch_dynamic(msg)
elif isinstance(self.solver, MultiCaptchaSolver):
self.dispatch_multicaptcha(msg)
else:
self.next(msg)
def dispatch_dynamic(self, msg):
if type(msg) is FinishChallenge:
if self.store.state.num_waiting <= 0:
self.send_solution(self.solver.finish())
elif type(msg) is SelectTile:
self.dynamic_select_tile(msg)
else:
self.next(msg)
def dynamic_select_tile(self, msg: SelectTile):
def select_tile():
with self._select_tile_lock:
tile = self.solver.select_tile(msg.index)
def replace():
self.next(ReplaceTile(index=msg.index, image=tile.image))
return False
GLib.timeout_add(round(tile.delay * 1000), replace)
self.next(ReplaceTile(index=msg.index, image=None))
if self.store.state.num_waiting <= 0:
raise RuntimeError("num_waiting should be greater than 0")
threading.Thread(target=select_tile, daemon=True).start()
def dispatch_multicaptcha(self, msg):
if type(msg) is FinishChallenge:
self.multicaptcha_finish()
else:
self.next(msg)
def multicaptcha_finish(self):
result = self.solver.select_indices(self.store.state.indices)
if isinstance(result, Solution):
self.send_solution(result)
elif isinstance(result, ImageGridChallenge):
self.next(SetNextChallenge(result))
else:
raise TypeError("Unexpected type: {}".format(type(result)))
def send_solution(self, solution: Solution):
self.solver = None
result = self.rc.send_solution(solution)
if not isinstance(result, str):
self.solver = result
result = state_from_solver(result)
self.next(SetState(result))
class WarningMiddleware:
def __init__(self, store: Store, next: Dispatch):
self.next = next
def dispatch(self, msg):
if type(msg) is SetNextChallenge:
self.check_goal(msg.challenge.goal)
elif type(msg) is SetState:
if type(msg.state) in SOLVER_STATE_TYPES:
self.check_goal(msg.state.challenge.goal)
self.next(msg)
def check_goal(self, goal: ChallengeGoal):
if goal.raw is not None:
return
msg = "WARNING: Could not determine challenge objective in: {}"
print(msg.format(goal.fallback), file=sys.stderr)
class DynamicState(namedtuple("DynamicState", [
"challenge", # Challenge
"tile_images", # List[Optional[Image.Image]]
"num_waiting", # int
])):
@classmethod
def from_new_solver(cls, solver: Solver):
challenge = solver.get_challenge()
tiles = list(tiles_from_image(challenge.image, challenge.dimensions))
return cls(
challenge=challenge,
tile_images=tiles,
num_waiting=0,
)
def replace_tile(
self,
index: int,
image: Optional[Image.Image],
) -> "DynamicState":
old_image = self.tile_images[index]
num_waiting = self.num_waiting
if (old_image is None) != (image is None):
num_waiting += 1 if image is None else -1
images = list(self.tile_images)
images[index] = image
return self._replace(tile_images=images, num_waiting=num_waiting)
def reduce(self, msg) -> "DynamicState":
if type(msg) is ReplaceTile:
return self.replace_tile(msg.index, msg.image)
return self
class MultiCaptchaState(namedtuple("MultiCaptchaState", [
"challenge", # Challenge
"tile_images", # List[Image.Image]
"selected", # List[bool]
])):
@classmethod
def from_new_solver(cls, solver: Solver):
return cls.from_challenge(solver.first_challenge())
@classmethod
def from_challenge(cls, challenge: ImageGridChallenge):
tiles = list(tiles_from_image(challenge.image, challenge.dimensions))
return cls(
challenge=challenge,
tile_images=tiles,
selected=([False] * challenge.dimensions.count),
)
def toggle_tile(self, index: int) -> "MultiCaptchaState":
selected = list(self.selected)
selected[index] ^= True
return self._replace(selected=selected)
@property
def indices(self) -> List[int]:
return [i for i, selected in enumerate(self.selected) if selected]
@property
def any_selected(self) -> bool:
return any(self.selected)
def same_any_selected(self, other) -> bool:
return type(self) is type(other) and (
self.selected is other.selected or
self.any_selected is other.any_selected
)
def reduce(self, msg) -> "MultiCaptchaState":
if type(msg) is SelectTile:
return self.toggle_tile(msg.index)
if type(msg) is SetNextChallenge:
return self.from_challenge(msg.challenge)
return self
SOLVER_STATE_TYPES = (MultiCaptchaState, DynamicState)
SolverState = Union[SOLVER_STATE_TYPES]
State = Union[SolverState, str, None]
class ImageGridChallengePres:
def __init__(self, state: SolverState):
self.state = state
def same(self, other) -> bool:
return (
type(self) is type(other) and
self.state is other.state
)
@property
def dimensions(self) -> GridDimensions:
return self.state.challenge.dimensions
@property
def goal(self) -> str:
raise NotImplementedError
def same_goal(self, other) -> bool:
raise NotImplementedError
@property
def verify_label(self) -> str:
return "Ver_ify"
def same_verify_label(self, other) -> bool:
return type(self) is type(other)
@property
def is_verify_enabled(self) -> bool:
return True
class DynamicPres(ImageGridChallengePres):
def __init__(self, state: DynamicState):
super().__init__(state)
@property
def goal(self) -> str:
note = "Click verify once there are none left."
return format_goal_with_note(self.state.challenge.goal, note)
# This method is more efficient than comparing `self.goal` and `other.goal`
# as it avoids formatting the goal strings.
def same_goal(self, other) -> bool:
return (
type(self) is type(other) and
self.state.challenge.goal is other.state.challenge.goal
)
@property
def is_verify_enabled(self) -> bool:
return self.state.num_waiting <= 0
@property
def tiles(self) -> Iterable["DynamicTilePres"]:
for i, image in enumerate(self.state.tile_images):
yield DynamicTilePres(index=i, image=image)
class MultiCaptchaPres(ImageGridChallengePres):
def __init__(self, state: MultiCaptchaState):
super().__init__(state)
@property
def goal(self) -> str:
note = "If there are none, click skip."
if any(self.state.selected):
note = '<span alpha="30%">{}</span>'.format(note)
return format_goal_with_note(self.state.challenge.goal, note)
# This method is more efficient than comparing `self.goal` and `other.goal`
# as it avoids formatting the goal strings.
def same_goal(self, other) -> bool:
return (
type(self) is type(other) and
self.state.challenge.goal is other.state.challenge.goal and
self.state.same_any_selected(other.state)
)
@property
def verify_label(self) -> str:
if self.state.any_selected:
return "Ver_ify"
return "Sk_ip"
def same_verify_label(self, other) -> bool:
return (
type(self) is type(other) and
self.state.same_any_selected(other.state)
)
@property
def tiles(self) -> Iterable["MultiCaptchaTilePres"]:
iterable = enumerate(zip(self.state.tile_images, self.state.selected))
for i, (image, selected) in iterable:
yield MultiCaptchaTilePres(index=i, image=image, selected=selected)
class TilePres:
index: int
image: Image.Image
def __init__(self, index: int, image: Image.Image):
self.index = index
self.image = image
def same(self, other) -> bool:
return (
type(self) is type(other) and
self.index == other.index and
self.image is other.image
)
def on_click(self, dispatch: Dispatch):
dispatch(SelectTile(index=self.index))
class DynamicTilePres(TilePres):
pass
class MultiCaptchaTilePres(TilePres):
selected: bool
def __init__(self, index: int, image: Image.Image, selected: bool):
super().__init__(index, image)
self.selected = selected
def same(self, other) -> bool:
return (
super().same(other) and
self.selected == other.selected
)

View File

@@ -0,0 +1,84 @@
# Copyright (C) 2017, 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from . import cli
from .errors import ChallengeBlockedError, UnknownChallengeError
from .errors import GtkImportError
from .recaptcha import ReCaptcha
__version__ = "0.7.4-dev"
GUI_MISSING_MESSAGE = """\
Error: Could not load the GUI. Is PyGObject installed?
Try (re)installing librecaptcha[gtk] with pip.
For more details, add the --debug option.
"""
CHALLENGE_BLOCKED_MESSAGE = """\
ERROR: Received challenge type "{}".
This is usually an indication that reCAPTCHA requests from this network are
being blocked.
Try installing Tor (the full installation, not just the browser bundle) and
running this program over Tor with the "torsocks" command.
Alternatively, try waiting a while before requesting another challenge over
this network.
"""
UNKNOWN_CHALLENGE_MESSAGE = """\
ERROR: Received unrecognized challenge type "{}".
Currently, the only supported challenge types are "dynamic" and "multicaptcha".
Please file an issue if this problem persists.
"""
def _get_gui():
from . import gui
return gui
def has_gui():
try:
_get_gui()
except GtkImportError:
return False
return True
def get_token(
api_key: str,
site_url: str,
user_agent: str, *,
gui=False,
debug=False,
) -> str:
ui = (_get_gui().Gui if gui else cli.Cli)(ReCaptcha(
api_key=api_key,
site_url=site_url,
user_agent=user_agent,
debug=debug,
))
try:
return ui.run()
except ChallengeBlockedError as e:
print(CHALLENGE_BLOCKED_MESSAGE.format(e.challenge_type))
raise
except UnknownChallengeError as e:
print(UNKNOWN_CHALLENGE_MESSAGE.format(e.challenge_type))
raise

View File

@@ -0,0 +1,621 @@
# Copyright (C) 2017, 2019, 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from .errors import ChallengeBlockedError, UnknownChallengeError
from .errors import SiteUrlParseError
from .extract_strings import extract_and_save
from .typing import Dict, Iterable, List, Tuple
import requests
from collections import namedtuple
from html.parser import HTMLParser
from typing import Optional, Union
from urllib.parse import urlparse
import base64
import io
import json
import os
import os.path
import re
import sys
import time
BASE_URL = "https://www.google.com/recaptcha/api2/"
API_JS_URL = "https://www.google.com/recaptcha/api.js"
JS_URL_TEMPLATE = """\
https://www.gstatic.com/recaptcha/releases/{}/recaptcha__{}.js
"""[:-1]
STRINGS_VERSION = "0.1.0"
STRINGS_PATH = os.path.join(
os.path.expanduser("~"), ".cache", "librecaptcha", "cached-strings",
)
CHALLENGE_GOAL_TEXT = {
'en': 'select all',
'it': 'seleziona tutti'
}
DYNAMIC_SELECT_DELAY = 4.5 # seconds
FIND_GOAL_SEARCH_DISTANCE = 10
def get_testing_url(url: str) -> str:
return urlparse(url)._replace(
scheme="http",
netloc="localhost:55476",
).geturl()
if os.getenv("LIBRECAPTCHA_USE_TEST_SERVER"):
BASE_URL = get_testing_url(BASE_URL)
API_JS_URL = get_testing_url(API_JS_URL)
JS_URL_TEMPLATE = get_testing_url(JS_URL_TEMPLATE)
def get_full_url(url: str) -> str:
return BASE_URL.rstrip("/") + "/" + url.lstrip("/")
def get_rc_site_url(url: str) -> str:
parsed = urlparse(url)
if not parsed.hostname:
raise SiteUrlParseError("Error: Site URL has no hostname.")
if not parsed.scheme:
raise SiteUrlParseError("Error: Site URL has no scheme.")
if parsed.scheme not in ["http", "https"]:
raise SiteUrlParseError(
"Error: Site URL has invalid scheme: {}".format(parsed.scheme),
)
port = parsed.port
if port is None:
port = {"http": 80, "https": 443}[parsed.scheme]
return "{}://{}:{}".format(parsed.scheme, parsed.hostname, port)
def rc_base64(string: str) -> str:
data = string
if isinstance(string, str):
data = string.encode()
return base64.b64encode(data, b"-_").decode().replace("=", ".")
def load_rc_json(text: str):
return json.loads(text.split("\n", 1)[1])
def get_meta(pmeta, probable_index: int):
if not isinstance(pmeta, list):
raise TypeError("pmeta is not a list: {!r}".format(pmeta))
def matches(meta):
return meta and isinstance(meta, list)
if probable_index < len(pmeta):
meta = pmeta[probable_index]
if matches(meta):
return meta
for child in pmeta:
if matches(child):
return child
raise RuntimeError("Could not find meta; pmeta: {!r}".format(pmeta))
def get_rresp(uvresp):
if not isinstance(uvresp, list):
raise TypeError("uvresp is not a list: {!r}".format(uvresp))
for child in uvresp:
if child and isinstance(child, list) and child[0] == "rresp":
return child
return None
def get_js_strings(user_agent: str, rc_version: str, lang : str = 'en') -> List[str]:
def get_json():
with open(STRINGS_PATH + '-' + lang) as f:
version, text = f.read().split("\n", 1)
if version != "{}/{}".format(STRINGS_VERSION, rc_version):
raise OSError("Incorrect version: {}".format(version))
return json.loads(text)
try:
return get_json()
except (OSError, ValueError, json.JSONDecodeError):
pass
result = extract_and_save(
url=JS_URL_TEMPLATE.format(rc_version, lang),
path=STRINGS_PATH + '-' + lang,
version=STRINGS_VERSION,
rc_version=rc_version,
user_agent=user_agent,
)
print(file=sys.stderr)
return result
def get_rc_version(user_agent: str) -> str:
match = re.search(r"/recaptcha/releases/(.+?)/", requests.get(
API_JS_URL, headers={
"User-Agent": user_agent,
},
).text)
if match is None:
raise RuntimeError("Could not extract version from api.js.")
return match.group(1)
# def get_image(data: bytes) -> Image.Image:
# image = Image.open(io.BytesIO(data))
# if image.mode in ["RGB", "RGBA"]:
# return image
# return image.convert("RGB")
def varint_encode(n: int, out: bytearray) -> bytes:
if n < 0:
raise ValueError("n must be nonnegative")
while True:
b = n & 127
n >>= 7
if n > 0:
out.append(b | 128)
else:
out.append(b)
break
def protobuf_encode(fields: Iterable[Tuple[int, bytes]]) -> bytes:
result = bytearray()
for num, value in fields:
# Wire type of 2 indicates a length-delimited field.
varint_encode((num << 3) | 2, result)
varint_encode(len(value), result)
result += value
return bytes(result)
def format_reload_protobuf(
rc_version: str,
token: str,
reason: str,
api_key: str,
) -> bytes:
# Note: We're not sending fields 3, 5, and 16.
return protobuf_encode([
(1, rc_version.encode()),
(2, token.encode()),
(6, reason.encode()),
(14, api_key.encode()),
])
class GridDimensions(namedtuple("GridDimensions", [
"rows", # int
"columns", # int
])):
@property
def count(self) -> int:
return self.rows * self.columns
Solution = namedtuple("Solution", [
"response",
])
ImageGridChallenge = namedtuple("ImageGridChallenge", [
"goal", # ChallengeGoal
"image", # Image.Image
"dimensions", # GridDimensions
])
DynamicTile = namedtuple("DynamicTile", [
"image", # Image.Image
"delay", # float
])
class DynamicSolver:
def __init__(self, recaptcha: "ReCaptcha", pmeta):
self.rc = recaptcha
self.selections = []
meta = get_meta(pmeta, 1)
self.meta = meta
self.tile_index_map = list(range(self.num_tiles))
self.last_request_map = [0] * self.num_tiles
self.latest_index = self.num_tiles - 1
self.challenge_retrieved = False
def get_challenge(self) -> ImageGridChallenge:
if self.challenge_retrieved:
raise RuntimeError("Challenge was already retrieved")
self.challenge_retrieved = True
goal = self.rc.get_challenge_goal(self.meta)
image = self._first_image()
return ImageGridChallenge(
goal=goal,
image=image,
dimensions=self.dimensions,
)
def select_tile(self, index: int) -> DynamicTile:
if not self.challenge_retrieved:
raise RuntimeError("Challenge must be retrieved first")
image = self._replace_tile(index)
delay = self.get_timeout(index)
return DynamicTile(image=image, delay=delay)
def finish(self) -> Solution:
if not self.challenge_retrieved:
raise RuntimeError("Challenge must be retrieved first")
return Solution(self.selections)
@property
def final_timeout(self):
return max(self.get_timeout(i) for i in range(self.num_tiles))
@property
def dimensions(self) -> GridDimensions:
return GridDimensions(rows=self.meta[3], columns=self.meta[4])
@property
def num_tiles(self):
return self.dimensions.count
def get_timeout(self, index: int):
elapsed = time.monotonic() - self.last_request_map[index]
duration = max(DYNAMIC_SELECT_DELAY - elapsed, 0)
return duration
def _first_image(self):
return self.rc.get("payload", params={
"p": None,
"k": None,
}).content
def _replace_tile(self, index: int):
real_index = self.tile_index_map[index]
self.selections.append(real_index)
r = self.rc.post("replaceimage", data={
"v": None,
"c": None,
"ds": "[{}]".format(real_index),
})
self.last_request_map[index] = time.monotonic()
data = load_rc_json(r.text)
self.latest_index += 1
self.tile_index_map[index] = self.latest_index
self.rc.current_token = data[1]
self.rc.current_p = data[5]
replacement_id = data[2][0]
# The server might not return any image, but it seems unlikely in
# practice. If it becomes a problem we can handle this case.
return self.rc.get("payload", params={
"p": None,
"k": None,
"id": replacement_id,
}).content
class MultiCaptchaSolver:
def __init__(self, recaptcha: "ReCaptcha", pmeta):
"""The current challenge."""
self.rc = recaptcha
self.selection_groups = []
self.challenge_type = None
self.id = "2"
self.metas = list(get_meta(pmeta, 5)[0])
self.challenge_index = -1
def first_challenge(self) -> ImageGridChallenge:
if self.challenge_index >= 0:
raise RuntimeError("Already retrieved first challenge")
return self._get_challenge(self._first_image())
def select_indices(self, indices) -> Union[ImageGridChallenge, Solution]:
if self.challenge_index < 0:
raise RuntimeError("First challenge wasn't retrieved")
self.selection_groups.append(list(sorted(indices)))
if not self.metas:
return Solution(self.selection_groups)
return self._get_challenge(self._replace_image())
def _get_challenge(self, image):
self.challenge_index += 1
meta = self.metas.pop(0)
dimensions = GridDimensions(rows=meta[3], columns=meta[4])
goal = self.rc.get_challenge_goal(meta)
return ImageGridChallenge(
goal=goal,
image=image,
dimensions=dimensions,
)
def _first_image(self):
return self.rc.get("payload", params={
"c": self.rc.current_token,
"k": self.rc.api_key,
}).content
def _replace_image(self):
selections = self.selection_groups[-1]
r = self.rc.post("replaceimage", data={
"v": None,
"c": self.rc.current_token,
"ds": json.dumps([selections], separators=",:"),
})
data = load_rc_json(r.text)
self.rc.current_token = data[1]
prev_p = self.rc.current_p
self.rc.current_p = data[5]
prev_id = self.id
self.id = (data[2] or [None])[0]
return self.rc.get("payload", params={
"p": prev_p,
"k": None,
"id": prev_id,
}).content
Solver = Union[DynamicSolver, MultiCaptchaSolver]
class ChallengeGoal(namedtuple("ChallengeGoal", [
"raw", # Optional[str]
"meta",
])):
@property
def plain(self) -> Optional[str]:
if self.raw is None:
return None
return self.raw.replace("<strong>", "").replace("</strong>", "")
@property
def fallback(self) -> str:
return json.dumps(self.meta)
class ReCaptcha:
def __init__(self, api_key, site_url, user_agent, debug=False,
make_requests=True, lang = 'en'):
self.api_key = api_key
self.site_url = get_rc_site_url(site_url)
self.debug = debug
self.co = rc_base64(self.site_url)
self.first_token = None
self.current_token = None
self.current_p = None
self.user_agent = user_agent
self.js_strings = None
self.rc_version = None
self.language = lang
if make_requests:
self.rc_version = get_rc_version(self.user_agent)
self.js_strings = get_js_strings(self.user_agent, self.rc_version, self.language)
self.solver_index = -1
def first_solver(self) -> Solver:
if self.solver_index >= 0:
raise RuntimeError("First solver was already retrieved")
self._request_first_token()
rresp = self._get_first_rresp()
return self._get_solver(rresp)
def send_solution(self, solution: Solution) -> Union[Solver, str]:
if self.solver_index < 0:
raise RuntimeError("First solver wasn't retrieved")
uvtoken, rresp = self._verify(solution.response)
if rresp is not None:
return self._get_solver(rresp)
if not uvtoken:
raise RuntimeError("Got neither uvtoken nor new rresp.")
return uvtoken
def debug_print(self, *args, **kwargs):
if not self.debug:
return
if len(args) == 1 and callable(args[0]):
args = (args[0](),)
print(*args, file=sys.stderr, **kwargs)
def get_challenge_goal(self, meta) -> ChallengeGoal:
raw = self.find_challenge_goal_text(meta[0])
return ChallengeGoal(raw=raw, meta=meta)
def find_challenge_goal_text(self, id: str, raw=False) -> str:
start = 0
matching_strings = []
def try_find():
nonlocal start
index = self.js_strings.index(id, start)
for i in range(FIND_GOAL_SEARCH_DISTANCE):
next_str = self.js_strings[index + i + 1]
if re.search(r"\b{}\b".format( CHALLENGE_GOAL_TEXT[ self.language] ) , next_str, re.I):
matching_strings.append((i, index, next_str))
start = index + FIND_GOAL_SEARCH_DISTANCE + 1
try:
while True:
try_find()
except (ValueError, IndexError):
pass
try:
goal = min(matching_strings)[2]
except ValueError:
return None
return goal
def get_headers(self, headers: Optional[Dict[str, str]]) -> Dict[str, str]:
headers = headers or {}
updates = {}
if "User-Agent" not in headers:
updates["User-Agent"] = self.user_agent
if updates:
headers = dict(headers)
headers.update(updates)
return headers
def get(self, url, *, params=None, headers=None, allow_errors=None,
**kwargs):
if params is None:
params = {"k": None, "v": None}
if params.get("k", "") is None:
params["k"] = self.api_key
if params.get("v", "") is None:
params["v"] = self.rc_version
if params.get("p", "") is None:
params["p"] = self.current_p
headers = self.get_headers(headers)
r = requests.get(
get_full_url(url), params=params, headers=headers,
**kwargs,
)
self.debug_print(lambda: "[http] [get] {}".format(r.url))
if not (allow_errors is True or r.status_code in (allow_errors or {})):
r.raise_for_status()
return r
def post(self, url, *, params=None, data=None, headers=None,
allow_errors=None, no_debug_response=False, **kwargs):
if params is None:
params = {"k": None}
if data is None:
data = {"v": None}
if params.get("k", "") is None:
params["k"] = self.api_key
if isinstance(data, dict) and data.get("v", "") is None:
data["v"] = self.rc_version
if isinstance(data, dict) and data.get("c", "") is None:
data["c"] = self.current_token
headers = self.get_headers(headers)
r = requests.post(
get_full_url(url), params=params, data=data, headers=headers,
**kwargs,
)
self.debug_print(lambda: "[http] [post] {}".format(r.url))
self.debug_print(lambda: "[http] [post] [data] {!r}".format(data))
if not no_debug_response:
self.debug_print(
lambda: "[http] [post] [response] {}".format(r.text),
)
if not (allow_errors is True or r.status_code in (allow_errors or {})):
r.raise_for_status()
return r
def _request_first_token(self):
class Parser(HTMLParser):
def __init__(p_self):
p_self.token = None
super().__init__()
def handle_starttag(p_self, tag, attrs):
attrs = dict(attrs)
if attrs.get("id") == "recaptcha-token":
p_self.token = attrs.get("value")
# Note: We're not sending "cb".
text = self.get("anchor", params={
"ar": "1",
"k": None,
"co": self.co,
"hl": "en",
"v": None,
"size": "normal",
"sa": "action",
}).text
parser = Parser()
parser.feed(text)
if not parser.token:
raise RuntimeError(
"Could not get first token. Response:\n{}".format(text),
)
self.current_token = parser.token
def _verify(self, response):
response_text = json.dumps({"response": response}, separators=",:")
response_b64 = rc_base64(response_text)
self.debug_print("Sending verify request...")
# Note: We're not sending "t", "ct", and "bg".
r = self.post("userverify", data={
"v": None,
"c": None,
"response": response_b64,
})
uvresp = load_rc_json(r.text)
self.debug_print(lambda: "Got verify response: {!r}".format(uvresp))
rresp = get_rresp(uvresp)
uvresp_token = uvresp[1]
return (uvresp_token, rresp)
def _get_first_rresp(self):
self.debug_print("Getting first rresp...")
r = self.post("reload", data=format_reload_protobuf(
rc_version=self.rc_version,
token=self.current_token,
reason="fi",
api_key=self.api_key,
), headers={
"Content-Type": "application/x-protobuffer",
})
rresp = load_rc_json(r.text)
self.debug_print(lambda: "Got first rresp: {!r}".format(rresp))
return rresp
def _get_solver(self, rresp) -> Solver:
self.solver_index += 1
challenge_type = rresp[5]
self.debug_print(lambda: "Challenge type: {}".format(challenge_type))
pmeta = rresp[4]
self.debug_print(lambda: "pmeta: {}".format(pmeta))
self.current_token = rresp[1]
self.current_p = rresp[9]
self.debug_print(
lambda: "Current token: {}".format(self.current_token),
)
solver_class = {
"dynamic": DynamicSolver,
"multicaptcha": MultiCaptchaSolver,
}.get(challenge_type)
if solver_class is not None:
return solver_class(self, pmeta)
if challenge_type in ["default", "doscaptcha"]:
raise ChallengeBlockedError(challenge_type)
raise UnknownChallengeError(challenge_type)

View File

@@ -0,0 +1,40 @@
# Copyright (C) 2021 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from collections.abc import Callable, Iterable
import typing
try:
list[int]
except Exception:
List = typing.List
Dict = typing.Dict
Tuple = typing.Tuple
else:
List = list
Dict = dict
Tuple = tuple
try:
Callable[[], int]
except Exception:
Callable = typing.Callable
try:
Iterable[int]
except Exception:
Iterable = typing.Iterable

View File

@@ -0,0 +1,36 @@
# This file was automatically generated by update_user_agents.py using data
# from <https://techblog.willshouse.com/2012/01/03/most-common-user-agents/>.
# flake8: noqa
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0",
"Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:85.0) Gecko/20100101 Firefox/85.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 OPR/73.0.3856.344"
]

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2017-2019 taylor.fish <contact@taylor.fish>
#
# This file is part of librecaptcha.
#
# librecaptcha is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# librecaptcha is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with librecaptcha. If not, see <https://www.gnu.org/licenses/>.
from .user_agent_data import USER_AGENTS
import random
def random_user_agent():
return random.choice(USER_AGENTS)

View File

@@ -38,9 +38,6 @@ from threading import Thread
__version__ = '1.7.0.dev0'
import xbmc
kodi_monitor = xbmc.Monitor()
major_version = sys.version_info[0]
if major_version < 3: # py <= 2.x
if sys.version_info[1] < 5: # py <= 2.4
@@ -443,7 +440,7 @@ class SqliteMultithread(Thread):
self._sqlitedict_thread_initialized = True
res = None
while not kodi_monitor.abortRequested():
while True:
req, arg, res, outer_stack = self.reqs.get()
if req == '--close--':
assert res, ('--close-- without return queue', res)

View File

@@ -438,4 +438,8 @@ def verify_directories_created():
def get_online_server_thumb(server):
return "https://raw.github.com/kodiondemand/media/master/resources/servers/" + server.lower().replace('_server','') + '.png'
return "https://raw.github.com/kodiondemand/media/master/resources/servers/" + server.lower().replace('_server','') + '.png'
def get_language():
return get_localized_string(20001)

View File

@@ -222,6 +222,13 @@ def run(item=None):
# Special action for findvideos, where the plugin looks for known urls
elif item.action == "findvideos":
from core import servertools
from core import db
if db['OnPlay'].get('addon', False):
item.autoplay = True
db['OnPlay']['addon'] = False
platformtools.fakeVideo()
db.close()
# First checks if channel has a "findvideos" function
if hasattr(channel, 'findvideos'):
@@ -292,9 +299,20 @@ def run(item=None):
platformtools.render_items(itemlist, item)
# For all other actions
# For all other actions
else:
# import web_pdb; web_pdb.set_trace()
if item.channel == 'filmontv':
reload = False
from core import db
if db['OnPlay'].get('addon', False):
reload = True
db['OnPlay']['addon'] = False
db.close()
if reload:
platformtools.fakeVideo()
import xbmc
return xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")")
logger.debug("Executing channel '%s' method" % item.action)
itemlist = getattr(channel, item.action)(item)
if config.get_setting('trakt_sync'):
@@ -468,12 +486,8 @@ def play_from_library(item):
import xbmcgui, xbmcplugin, xbmc
from time import sleep
# logger.debug("item: \n" + item.tostring('\n'))
# xbmc.Player().play(os.path.join(config.get_runtime_path(), "resources", "kod.mp4"))
if not item.autoplay and not item.next_ep:
platformtools.fakeVideo()
# from core.support import dbg;dbg()
# platformtools.prevent_busy(item)
itemlist=[]

View File

@@ -408,7 +408,8 @@ def viewmodeMonitor():
if currentMode != defaultMode:
# logger.debug('viewmode changed: ' + currentModeName + '-' + str(currentMode) + ' - content: ' + content)
config.set_setting('view_mode_%s' % content, currentModeName + ', ' + str(currentMode))
dialog_notification(config.get_localized_string(70153),
if config.get_setting('viewchange_notify'):
dialog_notification(config.get_localized_string(70153),
config.get_localized_string(70187) % (content, currentModeName),
sound=False)
except:
@@ -1138,8 +1139,8 @@ def show_video_info(*args, **kwargs):
def show_recaptcha(key, referer):
from platformcode.recaptcha import Recaptcha
return Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(key, referer)
from platformcode.recaptcha import Kodi
return Kodi(key, referer).run()
def alert_no_disponible_server(server):
@@ -1489,39 +1490,27 @@ def play_torrent(item, xlistitem, mediaurl):
selection = 0
if selection >= 0:
prevent_busy()
prevent_busy(item)
mediaurl = urllib.quote_plus(item.url)
torr_client = torrent_options[selection][0]
if torr_client in ['elementum'] and item.infoLabels['tmdb_id']:
if item.contentType == 'episode' and "elementum" not in torr_client:
mediaurl += "&episode=%s&library=&season=%s&show=%s&tmdb=%s&type=episode" % (item.infoLabels['episode'], item.infoLabels['season'], item.infoLabels['tmdb_id'], item.infoLabels['tmdb_id'])
mediaurl += "&episode=%s&season=%s&show=%s&tmdb=%s&type=episode" % (item.infoLabels['episode'], item.infoLabels['season'], item.infoLabels['tmdb_id'], item.infoLabels['tmdb_id'])
elif item.contentType == 'movie':
mediaurl += "&library=&tmdb=%s&type=movie" % (item.infoLabels['tmdb_id'])
mediaurl += "&tmdb=%s&type=movie" % (item.infoLabels['tmdb_id'])
if torr_client in ['elementum'] and item.downloadFilename:
torrent.elementum_download(item)
else:
# xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")")
if (item.fromLibrary and item.play_from == 'window') or item.window:
xlistitem.setPath(torrent_options[selection][1] % mediaurl)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(torrent_options[selection][1] % mediaurl, xlistitem)
xbmc_player.play(playlist, xlistitem)
else:
if not item.autoplay and item.channel != 'videolibrary': fakeVideo()
if xbmc.getCondVisibility("system.platform.android"): xbmc.sleep(3000)
xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")")
# torrent.mark_auto_as_watched(item)
# if not item.globalsearch:
# while is_playing() and not xbmc.Monitor().abortRequested():
# time.sleep(3)
import xbmcaddon
addon = xbmcaddon.Addon(id='plugin.video.elementum')
if addon.getSetting('download_storage') == '0':
addon.setSetting('download_storage', '1')
xbmc.sleep(3000)
xbmc.executebuiltin("PlayMedia(" + torrent_options[selection][1] % mediaurl + ")")
def resume_playback(played_time):
class ResumePlayback(xbmcgui.WindowXMLDialog):
@@ -1597,173 +1586,38 @@ def install_inputstream():
def install_widevine():
platform = get_platform()
if platform['os'] != 'android':
from core.httptools import downloadpage
from xbmcaddon import Addon
from core import jsontools
from distutils.version import LooseVersion
path = xbmc.translatePath(Addon('inputstream.adaptive').getSetting('DECRYPTERPATH'))
# if Widevine CDM is not installed
if not os.path.exists(path) or not os.listdir(path):
select = dialog_yesno('Widevine CDM', config.get_localized_string(70808))
if select > 0:
if not 'arm' in platform['arch']:
last_version = downloadpage('https://dl.google.com/widevine-cdm/versions.txt').data.split()[-1]
download_widevine(last_version, platform, path)
else:
json = downloadpage('https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.json').data
devices = jsontools.load(json)
download_chromeos_image(devices, platform, path)
# if Widevine CDM is outdated
elif platform['os'] != 'android':
if not 'arm' in platform['arch']:
last_version = downloadpage('https://dl.google.com/widevine-cdm/versions.txt').data.split()[-1]
current_version = jsontools.load(open(os.path.join(path, 'manifest.json')).read())['version']
if LooseVersion(last_version) > LooseVersion(current_version):
select = dialog_yesno(config.get_localized_string(70810),config.get_localized_string(70809))
if select > 0: download_widevine(last_version, platform, path)
else:
devices = jsontools.load(downloadpage('https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.json').data)
current_version = jsontools.load(open(os.path.join(path, 'config.json')).read())['version']
last_version = best_chromeos_image(devices)['version']
if LooseVersion(last_version) > LooseVersion(current_version):
select = dialog_yesno(config.get_localized_string(70810),config.get_localized_string(70809))
if select > 0:download_chromeos_image(devices, platform, path)
def download_widevine(version, platform, path):
# for x86 architectures
from zipfile import ZipFile
from core import downloadtools
archiveName = 'https://dl.google.com/widevine-cdm/' + version + '-' + platform['os'] + '-' + platform['arch'] + '.zip'
fileName = config.get_temp_file('widevine.zip')
if not os.path.exists(archiveName):
if not os.path.exists(fileName):
downloadtools.downloadfile(archiveName, fileName, header='Download Widevine CDM')
zip_obj = ZipFile(fileName)
for filename in zip_obj.namelist():
zip_obj.extract(filename, path)
zip_obj.close()
os.remove(fileName)
def download_chromeos_image(devices, platform, path):
# for arm architectures
from core import downloadtools
from core import jsontools
best = best_chromeos_image(devices)
archiveName = best['url']
version = best['version']
fileName = config.get_temp_file(archiveName.split('/')[-1])
if not os.path.exists(fileName):
downloadtools.downloadfile(archiveName, fileName, header='Download Widevine CDM')
from lib.arm_chromeos import ChromeOSImage
ChromeOSImage(fileName).extract_file(
filename='libwidevinecdm.so',
extract_path=os.path.join(path),
progress=dialog_progress(config.get_localized_string(70811),config.get_localized_string(70812)))
recovery_file = os.path.join(path, os.path.basename('https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.json'))
config_file = os.path.join(path, 'config.json')
if not os.path.exists(path):
os.mkdir(path)
with open(recovery_file, 'w') as reco_file:
reco_file.write(jsontools.dump(devices, indent=4))
reco_file.close()
with open(config_file, 'w') as conf_file:
conf_file.write(jsontools.dump(best))
conf_file.close()
os.remove(fileName)
def best_chromeos_image(devices):
best = None
for device in devices:
# Select ARM hardware only
for arm_hwid in ['BIG','BLAZE','BOB','DRUWL','DUMO','ELM','EXPRESSO','FIEVEL','HANA','JAQ','JERRY','KEVIN','KITTY','MICKEY','MIGHTY','MINNIE','PHASER','PHASER360','PI','PIT','RELM','SCARLET','SKATE','SNOW','SPEEDY','SPRING','TIGER']:
if arm_hwid in device['hwidmatch']:
hwid = arm_hwid
break # We found an ARM device, rejoice !
else:
continue # Not ARM, skip this device
device['hwid'] = hwid
# Select the first ARM device
if best is None:
best = device
continue # Go to the next device
# Skip identical hwid
if hwid == best['hwid']:
continue
# Select the newest version
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module,useless-suppression
if LooseVersion(device['version']) > LooseVersion(best['version']):
logger.info('%s (%s) is newer than %s (%s)' % (device['hwid'], device['version'], best['hwid'], best['version']))
best = device
# Select the smallest image (disk space requirement)
elif LooseVersion(device['version']) == LooseVersion(best['version']):
if int(device['filesize']) + int(device['zipfilesize']) < int(best['filesize']) + int(best['zipfilesize']):
logger.info('%s (%d) is smaller than %s (%d)' % (device['hwid'], int(device['filesize']) + int(device['zipfilesize']), best['hwid'], int(best['filesize']) + int(best['zipfilesize'])))
best = device
return best
def get_platform():
import platform
build = xbmc.getInfoLabel("System.BuildVersion")
kodi_version = int(build.split()[0][:2])
ret = {
"auto_arch": sys.maxsize > 2 ** 32 and "64-bit" or "32-bit",
"arch": sys.maxsize > 2 ** 32 and "x64" or "ia32",
"os": "",
"version": platform.release(),
"kodi": kodi_version,
"build": build
}
# Not necessary on Android devices
if xbmc.getCondVisibility("system.platform.android"):
ret["os"] = "android"
if "arm" in platform.machine() or "aarch" in platform.machine():
ret["arch"] = "arm"
if "64" in platform.machine() and ret["auto_arch"] == "64-bit":
ret["arch"] = "arm64"
elif xbmc.getCondVisibility("system.platform.linux"):
ret["os"] = "linux"
if "aarch" in platform.machine() or "arm64" in platform.machine():
if xbmc.getCondVisibility("system.platform.linux.raspberrypi"):
ret["arch"] = "armv7"
elif ret["auto_arch"] == "32-bit":
ret["arch"] = "armv7"
elif ret["auto_arch"] == "64-bit":
ret["arch"] = "arm64"
elif platform.architecture()[0].startswith("32"):
ret["arch"] = "arm"
else:
ret["arch"] = "arm64"
elif "armv7" in platform.machine():
ret["arch"] = "armv7"
elif "arm" in platform.machine():
ret["arch"] = "arm"
elif xbmc.getCondVisibility("system.platform.xbox"):
ret["os"] = "win"
ret["arch"] = "x64"
elif xbmc.getCondVisibility("system.platform.windows"):
ret["os"] = "win"
if platform.machine().endswith('64'):
ret["arch"] = "x64"
elif xbmc.getCondVisibility("system.platform.osx"):
ret["os"] = "mac"
ret["arch"] = "x64"
elif xbmc.getCondVisibility("system.platform.ios"):
ret["os"] = "ios"
ret["arch"] = "arm"
return
return ret
# For all other devices use InputSeream Helper to install or update Widevine
from core import filetools
from xbmcaddon import Addon
addonName = 'script.module.inputstreamhelper'
def isHelper():
# If InputStream Helper is not installed requires installation
ret = False
if filetools.exists(xbmc.translatePath('special://home/addons/{}'.format(addonName))):
ret = True
else:
xbmc.executebuiltin('InstallAddon({})'.format(addonName), wait=True)
try:
addon = Addon(id=addonName)
ret = True
except:
pass
return ret
# If InputStream Helper is installed, install or update Widevine
if isHelper():
addon = Addon(id=addonName)
path = filetools.join(addon.getAddonInfo('Path'), 'lib')
sys.path.append(path)
from inputstreamhelper import Helper
helper = Helper('mpd', drm='widevine')
if not helper._check_widevine():
helper.install_widevine()
def get_played_time(item):
@@ -1831,7 +1685,9 @@ def prevent_busy(item=None):
def fakeVideo():
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=os.path.join(config.get_runtime_path(), "resources", "kod.mp4")))
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True,
xbmcgui.ListItem(path=os.path.join(config.get_runtime_path(), "resources", "kod.mp4")))
sleep = 200
while not is_playing():
xbmc.sleep(10)
xbmc.Player().stop()
xbmc.sleep(sleep)
xbmc.Player().stop()

View File

@@ -1,77 +1,211 @@
# -*- coding: utf-8 -*-
import sys
import time
from threading import Thread
from builtins import range
import xbmcgui
from core import httptools
from core import scrapertools
from platformcode import config
from platformcode import platformtools
from core import filetools
from platformcode import config, platformtools
from platformcode import logger
from lib.librecaptcha.recaptcha import ReCaptcha, Solver, DynamicSolver, MultiCaptchaSolver, Solution, \
ImageGridChallenge
lang = 'it'
temp_dir = config.get_temp_file('reCAPTCHA/')
tiles_pos = (75+390, 90+40)
grid_width = 450
tiles_texture_focus = 'white.png'
tiles_texture_checked = 'Controls/check_mark.png'
cur_tmp = 0
class Recaptcha(xbmcgui.WindowXMLDialog):
def Start(self, key, referer):
self.referer = referer
self.key = key
self.headers = {'Referer': self.referer}
TITLE = 10
PANEL = 11
IMAGE = 12
CONTROL = 1
api_js = httptools.downloadpage("https://www.google.com/recaptcha/api.js?hl=" + lang).data
version = scrapertools.find_single_match(api_js, 'po.src\s*=\s*\'(.*?)\';').split("/")[5]
self.url = "https://www.google.com/recaptcha/api/fallback?k=" + self.key + "&hl=" + lang + "&v=" + version + "&t=2&ff=true"
self.doModal()
# Reload
if self.result == {}:
self.result = Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(self.key, self.referer)
OK = 21
CANCEL = 22
# RELOAD = 23
return self.result
def update_window(self):
data = httptools.downloadpage(self.url, headers=self.headers).data
self.message = scrapertools.find_single_match(data,
'<div class="rc-imageselect-desc[a-z-]*">(.*?)(?:</label>|</div>)').replace(
"<strong>", "[B]").replace("</strong>", "[/B]")
self.token = scrapertools.find_single_match(data, 'name="c" value="([^"]+)"')
self.image = "https://www.google.com/recaptcha/api2/payload?k=%s&c=%s" % (self.key, self.token)
self.result = {}
self.getControl(10020).setImage(self.image)
self.getControl(10000).setText(self.message)
self.setFocusId(10005)
def get_temp():
global cur_tmp
cur_tmp += 1
if not filetools.isdir(temp_dir):
filetools.mkdir(temp_dir)
return temp_dir + str(cur_tmp) + '.png'
class Kodi:
def __init__(self, key, referer):
if sys.version_info[0] < 3:
self.rc = None
platformtools.dialog_ok('reCAPTCHA', 'Il sito sta mostrando la schermata "Non sono un robot".\nQuesta schermata tuttavia è superabile solo da kodi 19')
else:
prog = platformtools.dialog_progress('Caricamento reCAPTCHA', 'Il sito sta mostrando la schermata "Non sono un robot"')
filetools.rmdirtree(temp_dir)
self.rc = ReCaptcha(
api_key=key,
site_url=referer,
user_agent=httptools.get_user_agent(),
lang=lang
)
prog.close()
def run(self):
if not self.rc:
return None
result = self.rc.first_solver()
while not isinstance(result, str) and result is not False:
solution = self.run_solver(result)
if solution:
result = self.rc.send_solution(solution)
logger.debug(result)
else:
return False
platformtools.dialog_notification("Captcha corretto", "Verifica conclusa")
return result
def run_solver(self, solver: Solver) -> Solution:
selected_solver = {
DynamicSolver: DynamicKodi,
MultiCaptchaSolver: MultiCaptchaKodi,
}[type(solver)]("Recaptcha.xml", config.get_runtime_path())
selected_solver.solver = solver
return selected_solver.run()
class SolverKodi(xbmcgui.WindowXMLDialog):
def __init__(self, *args, **kwargs):
self.mensaje = kwargs.get("mensaje")
self.imagen = kwargs.get("imagen")
self.goal = ""
self.closed = False
self.result = None
self.image_path = ''
self.indices = {}
self.num_rows = 3
self.num_columns = 3
self.num_tiles = 9
logger.debug()
def show_image(self, image, goal):
self.image_path = get_temp()
filetools.write(self.image_path, image)
self.goal = goal.replace('<strong>', '[B]').replace('</strong>', '[/B]')
self.doModal()
def onInit(self):
#### Kodi 18 compatibility ####
if config.get_platform(True)['num_version'] < 18:
self.setCoordinateResolution(2)
self.update_window()
logger.debug(self.image_path)
items=[]
self.getControl(IMAGE).setImage(self.image_path, False)
self.getControl(TITLE).setLabel(self.goal)
for x in range(self.num_tiles):
item = xbmcgui.ListItem('')
item.setProperty('selected', 'false')
items.append(item)
self.getControl(PANEL).reset()
self.getControl(PANEL).addItems(items)
class MultiCaptchaKodi(SolverKodi):
"""
multicaptcha challenges present you with one large image split into a grid of tiles and ask you to select the tiles that contain a given object.
Occasionally, the image will not contain the object, but rather something that looks similar.
It is possible to select no tiles in this case, but reCAPTCHA may have been fooled by the similar-looking object and would reject a selection of no tiles.
"""
def run(self):
result = self.solver.first_challenge()
while not isinstance(result, Solution):
if not isinstance(result, ImageGridChallenge):
raise TypeError("Unexpected type: {}".format(type(result)))
indices = self.handle_challenge(result)
if self.closed:
return False
result = self.solver.select_indices(indices)
return result
def handle_challenge(self, challenge: ImageGridChallenge):
goal = challenge.goal.plain
self.num_rows = challenge.dimensions.rows
self.num_columns = challenge.dimensions.columns
logger.debug('RIGHE',self.num_rows, 'COLONNE',self.num_columns)
self.num_tiles = challenge.dimensions.count
image = challenge.image
self.show_image(image, goal)
if self.closed:
return False
return self.result
def onClick(self, control):
if control == 10003:
self.result = None
if control == CANCEL:
self.closed = True
self.close()
elif control == 10004:
self.result = {}
# elif control == RELOAD:
# self.closed = True
# self.close()
elif control == OK:
self.result = [int(k) for k in range(self.num_tiles) if self.indices.get(k, False)]
self.close()
elif control == 10002:
self.result = [int(k) for k in range(9) if self.result.get(k, False)]
post = {
"c": self.token,
"response": self.result
}
data = httptools.downloadpage(self.url, post=post, headers=self.headers).data
from platformcode import logger
logger.debug(data)
self.result = scrapertools.find_single_match(data, '<div class="fbc-verification-token">.*?>([^<]+)<')
if self.result:
platformtools.dialog_notification("Captcha corretto", "Verifica conclusa")
self.close()
else:
self.result = {}
self.close()
else:
self.result[control - 10005] = not self.result.get(control - 10005, False)
item = self.getControl(PANEL)
index = item.getSelectedPosition()
selected = True if item.getSelectedItem().getProperty('selected') == 'false' else False
item.getSelectedItem().setProperty('selected', str(selected).lower())
self.indices[index] = selected
class DynamicKodi(SolverKodi):
"""
dynamic challenges present you with a grid of different images and ask you to select the images that match the given description.
Each time you click an image, a new one takes its place. Usually, three images from the initial set match the description,
and at least one of the replacement images does as well.
"""
def run(self):
challenge = self.solver.get_challenge()
image = challenge.image
goal = challenge.goal.raw
self.num_rows = challenge.dimensions.rows
self.num_columns = challenge.dimensions.columns
self.num_tiles = challenge.dimensions.count
logger.debug('RIGHE',self.num_rows, 'COLONNE',self.num_columns)
self.show_image(image, goal)
if self.closed:
return False
return self.result
def changeTile(self, item, path, delay):
cur_delay = delay
while cur_delay > 0:
# todo: show time
item.setLabel('{:.1f}'.format(cur_delay))
time.sleep(0.1)
cur_delay -= 0.1
item.setLabel('')
item.setArt({'image': path})
def onClick(self, control):
if control == CANCEL:
self.closed = True
self.close()
# elif control == RELOAD:
# self.result = None
# self.close()
elif control == OK:
self.result = self.solver.finish()
self.close()
else:
panel = self.getControl(PANEL)
item = panel.getSelectedItem()
if not item.getLabel():
index = panel.getSelectedPosition()
tile = self.solver.select_tile(index)
path = get_temp()
filetools.write(path, tile.image)
Thread(target=self.changeTile, args=(item, path, tile.delay)).start()

View File

@@ -306,7 +306,7 @@ def updateFromZip(message=config.get_localized_string(80050)):
def refreshLang():
from platformcode import config
language = config.get_localized_string(20001)
language = config.get_language()
if language == 'eng':
xbmc.executebuiltin("SetGUILanguage(resource.language.it_it)")
xbmc.executebuiltin("SetGUILanguage(resource.language.en_en)")

View File

@@ -34,6 +34,7 @@ def mark_auto_as_watched(item):
sync = False
next_episode = None
show_server = True
mark_time = 0
percentage = float(config.get_setting("watched_setting")) / 100
time_from_end = config.get_setting('next_ep_seconds')
@@ -85,7 +86,7 @@ def mark_auto_as_watched(item):
break
# if item.options['continue']:
if actual_time < mark_time:
if actual_time < mark_time and mark_time:
item.played_time = actual_time
else: item.played_time = 0
platformtools.set_played_time(item)

Binary file not shown.

View File

@@ -4857,7 +4857,7 @@ msgid "Insert page number"
msgstr ""
msgctxt "#70514"
msgid ""
msgid "Notify current view saves"
msgstr ""
msgctxt "#70515"

View File

@@ -4856,8 +4856,8 @@ msgid "Insert page number"
msgstr "Inserisci numero di pagina"
msgctxt "#70514"
msgid ""
msgstr ""
msgid "Notify current view saves"
msgstr "Notifica i salvataggi della vista attuale"
msgctxt "#70515"
msgid "Completed %s"

View File

@@ -136,6 +136,7 @@
<setting id="enable_library_menu" label="30131" type="bool" default="true"/>
<setting label="30000" type="lsep"/>
<setting id="touch_view" label='30002' type="bool" default="false"/>
<setting id="viewchange_notify" label='70514' type="bool" default="true"/>
<!-- View Mode (hidden)-->
<setting id="skin_name" label='Skin Name' type="text" default="skin.estuary" visible="false"/>
<setting id="view_mode_home" type="action" label="70009" default= "Default, 0" visible="false"/>

View File

@@ -1,223 +1,245 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<window>
<allowoverlays>false</allowoverlays>
<animation type="WindowOpen" reversible="false">
<effect type="zoom" start="80" end="100" center="640,225" delay="160" tween="back" time="240" />
<effect type="fade" delay="160" end="100" time="240" />
<defaultcontrol>11</defaultcontrol>
<animation type='WindowOpen' reversible='false'>
<effect type='zoom' start='80' end='100' center='640,225' delay='160' tween='back' time='240' />
<effect type='fade' delay='160' end='100' time='240' />
</animation>
<animation type="WindowClose" reversible="false">
<effect type="zoom" start="100" end="80" center="640,225" easing="in" tween="back" time="240" />
<effect type="fade" start="100" end="0" time="240" />
<animation type='WindowClose' reversible='false'>
<effect type='zoom' start='100' end='80' center='640,225' easing='in' tween='back' time='240' />
<effect type='fade' start='100' end='0' time='240' />
</animation>
<controls>
<control type="group" id="10001">
<top>40</top>
<left>390</left>
<width>600</width>
<height>640</height>
<control type="image">
<width>510</width>
<height>640</height>
<left>45</left>
<texture>Controls/dialog-bg-solid.png</texture>
<control type='group'>
<top>40</top>
<left>398</left>
<width>484</width>
<height>640</height>
<control type='image'>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='FF232323'>white.png</texture>
</control>
<control type='image' id='12'>
<description>Main Captcha Image</description>
<top>90</top>
<left>20</left>
<width>444</width>
<height>444</height>
</control>
<control type='label' id='10'>
<top>20</top>
<left>20</left>
<height>60</height>
<width>444</width>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<wrapmultiline>true</wrapmultiline>
<align>center</align>
<aligny>center</aligny>
<label></label>
</control>
<control type='panel' id='11'>
<left>20</left>
<top>90</top>
<ondown>21</ondown>
<itemlayout height='148' width='148' condition='Integer.IsEqual(Container(11).NumItems, 9)'>
<control type='image'>
<description>Override Image</description>
<width>100%</width>
<height>100%</height>
<texture>$INFO[ListItem.Art(image)]</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='FFFFFFFF' border='2'>selection.png</texture>
</control>
<control type='image'>
<visible>!String.IsEmpty(ListItem.Label)</visible>
<description>Hide Image</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='88000000'>white.png</texture>
</control>
<control type='label'>
<description>Remaining Time</description>
<width>100%</width>
<height>100%</height>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<label>[B]$INFO[Listitem.Label][/B]</label>
<aligny>center</aligny>
<align>center</align>
</control>
<control type='image'>
<visible>String.IsEqual(ListItem.Property(selected), true)</visible>
<description>Selected Image</description>
<width>100%</width>
<height>100%</height>
<texture>Controls/check_mark.png</texture>
</control>
</itemlayout>
<itemlayout height='111' width='111'>
<control type='image'>
<description>Override Image</description>
<width>100%</width>
<height>100%</height>
<texture>$INFO[ListItem.Art(image)]</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='FFFFFFFF' border='2'>selection.png</texture>
</control>
<control type='image'>
<visible>!String.IsEmpty(ListItem.Label)</visible>
<description>Hide Image</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='88000000'>white.png</texture>
</control>
<control type='label'>
<description>Remaining Time</description>
<width>100%</width>
<height>100%</height>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<label>[B]$INFO[Listitem.Label][/B]</label>
<aligny>center</aligny>
<align>center</align>
</control>
<control type='image'>
<visible>String.IsEqual(ListItem.Property(selected), true)</visible>
<description>Selected Image</description>
<width>100%</width>
<height>100%</height>
<texture>Controls/check_mark.png</texture>
</control>
</itemlayout>
<focusedlayout height='148' width='148' condition='Integer.IsEqual(Container(11).NumItems, 9)'>
<control type='image'>
<description>Override Image</description>
<width>100%</width>
<height>100%</height>
<texture>$INFO[ListItem.Art(image)]</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='FF0082C2' border='2'>selection.png</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='550082C2'>white.png</texture>
</control>
<control type='image'>
<visible>!String.IsEmpty(ListItem.Label)</visible>
<description>Hide Image</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='88000000'>white.png</texture>
</control>
<control type='label'>
<description>Remaining Time</description>
<width>100%</width>
<height>100%</height>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<label>[B]$INFO[Listitem.Label][/B]</label>
<aligny>center</aligny>
<align>center</align>
</control>
<control type='image'>
<visible>String.IsEqual(ListItem.Property(selected), true)</visible>
<description>Selected Image</description>
<width>100%</width>
<height>100%</height>
<texture>Controls/check_mark.png</texture>
</control>
</focusedlayout>
<focusedlayout height='111' width='111'>
<control type='image'>
<description>Override Image</description>
<width>100%</width>
<height>100%</height>
<texture>$INFO[ListItem.Art(image)]</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='880082C2' border='2'>selection.png</texture>
</control>
<control type='image'>
<description>Focused</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='550082C2'>white.png</texture>
</control>
<control type='image'>
<visible>!String.IsEmpty(ListItem.Label)</visible>
<description>Hide Image</description>
<width>100%</width>
<height>100%</height>
<texture colordiffuse='88000000'>white.png</texture>
</control>
<control type='label'>
<description>Remaining Time</description>
<width>100%</width>
<height>100%</height>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<label>[B]$INFO[Listitem.Label][/B]</label>
<aligny>center</aligny>
<align>center</align>
</control>
<control type='image'>
<visible>String.IsEqual(ListItem.Property(selected), true)</visible>
<description>Selected Image</description>
<width>100%</width>
<height>100%</height>
<texture>Controls/check_mark.png</texture>
</control>
</focusedlayout>
</control>
<control type='grouplist'>
<top>565</top>
<left>20</left>
<orientation>horizontal</orientation>
<control type='button' id='21'>
<width>222</width>
<height>50</height>
<onup>11</onup>
<textwidth>110</textwidth>
<textcolor>FFFFFFFF</textcolor>
<focusedcolor>FFFFFFFF</focusedcolor>
<texturefocus colordiffuse='FF0082C2'>white.png</texturefocus>
<texturenofocus colordiffuse='000082C2'>white.png</texturenofocus>
<align>center</align>
<aligny>center</aligny>
<label>$ADDON[plugin.video.kod 70007]</label>
</control>
<control type='button' id='22'>
<width>222</width>
<height>50</height>
<onup>11</onup>
<textwidth>110</textwidth>
<textcolor>FFFFFFFF</textcolor>
<focusedcolor>FFFFFFFF</focusedcolor>
<texturefocus colordiffuse='FF0082C2'>white.png</texturefocus>
<texturenofocus colordiffuse='000082C2'>white.png</texturenofocus>
<align>center</align>
<aligny>center</aligny>
<label>$ADDON[plugin.video.kod 707433]</label>
</control>
</control>
</control>
<control type="textbox" id="10000">
<top>30</top>
<left>20</left>
<height>60</height>
<width>560</width>
<font>font13</font>
<textcolor>FFFFFFFF</textcolor>
<wrapmultiline>true</wrapmultiline>
<align>center</align>
<label></label>
</control>
<control type="button" id="10002">
<top>565</top>
<left>75</left>
<width>150</width>
<height>50</height>
<textwidth>110</textwidth>
<textcolor>FFFFFFFF</textcolor>
<focusedcolor>FFFFFFFF</focusedcolor>
<texturefocus colordiffuse="FF0082C2">white.png</texturefocus>
<texturenofocus colordiffuse="000082C2">white.png</texturenofocus>
<align>center</align>
<aligny>center</aligny>
<label>$ADDON[plugin.video.kod 70007]</label>
<onup>10011</onup>
<ondown>10005</ondown>
<onleft>10004</onleft>
<onright>10003</onright>
</control>
<control type="button" id="10003">
<top>565</top>
<left>225</left>
<width>150</width>
<height>50</height>
<textwidth>110</textwidth>
<textcolor>FFFFFFFF</textcolor>
<focusedcolor>FFFFFFFF</focusedcolor>
<texturefocus colordiffuse="FF0082C2">white.png</texturefocus>
<texturenofocus colordiffuse="000082C2">white.png</texturenofocus>
<align>center</align>
<aligny>center</aligny>
<label>$ADDON[plugin.video.kod 707433]</label>
<onup>10012</onup>
<ondown>10006</ondown>
<onleft>10002</onleft>
<onright>10004</onright>
</control>
<control type="button" id="10004">
<top>565</top>
<left>375</left>
<width>150</width>
<height>50</height>
<textwidth>110</textwidth>
<textcolor>FFFFFFFF</textcolor>
<focusedcolor>FFFFFFFF</focusedcolor>
<texturefocus colordiffuse="FF0082C2">white.png</texturefocus>
<texturenofocus colordiffuse="000082C2">white.png</texturenofocus>
<align>center</align>
<aligny>center</aligny>
<label>$ADDON[plugin.video.kod 70008]</label>
<onup>10013</onup>
<ondown>10007</ondown>
<onleft>10003</onleft>
<onright>10002</onright>
</control>
<control type="image" id="10020">
<top>90</top>
<left>75</left>
<width>450</width>
<height>450</height>
</control>
<control type="togglebutton" id="10005">
<top>90</top>
<left>75</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10002</onup>
<ondown>10008</ondown>
<onleft>10007</onleft>
<onright>10006</onright>
</control>
<control type="togglebutton" id="10006">
<top>90</top>
<left>225</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10003</onup>
<ondown>10009</ondown>
<onleft>10005</onleft>
<onright>10007</onright>
</control>
<control type="togglebutton" id="10007">
<top>90</top>
<left>375</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10004</onup>
<ondown>10010</ondown>
<onleft>10006</onleft>
<onright>10005</onright>
</control>
<control type="togglebutton" id="10008">
<top>240</top>
<left>75</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10005</onup>
<ondown>10011</ondown>
<onleft>10010</onleft>
<onright>10009</onright>
</control>
<control type="togglebutton" id="10009">
<top>240</top>
<left>225</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10006</onup>
<ondown>10012</ondown>
<onleft>10008</onleft>
<onright>10010</onright>
</control>
<control type="togglebutton" id="10010">
<top>240</top>
<left>375</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10007</onup>
<ondown>10013</ondown>
<onleft>10009</onleft>
<onright>10008</onright>
</control>
<control type="togglebutton" id="10011">
<top>390</top>
<left>75</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10008</onup>
<ondown>10002</ondown>
<onleft>10013</onleft>
<onright>10012</onright>
</control>
<control type="togglebutton" id="10012">
<top>390</top>
<left>225</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10009</onup>
<ondown>10003</ondown>
<onleft>10011</onleft>
<onright>10013</onright>
</control>
<control type="togglebutton" id="10013">
<top>390</top>
<left>375</left>
<width>150</width>
<height>150</height>
<texturefocus colordiffuse="AA232323">white.png</texturefocus>
<texturenofocus colordiffuse="00232323">white.png</texturenofocus>
<alttexturefocus colordiffuse="FF0082C2">Controls/check_mark.png</alttexturefocus>
<alttexturenofocus colordiffuse="FFFFFFFF">Controls/check_mark.png</alttexturenofocus>
<onup>10010</onup>
<ondown>10004</ondown>
<onleft>10012</onleft>
<onright>10011</onright>
</control>
</control>
</controls>
</window>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -4,12 +4,8 @@
"ignore_urls": [],
"patterns": [
{
"pattern": "(https?://maxstream.video/uprot/[a-zA-Z0-9]+)",
"pattern": "(https?://maxstream.video/(?:[^/]+/)?([a-zA-Z0-9]+))",
"url": "\\1"
},
{
"pattern": "https?://maxstream.video/(?:e/|embed-|cast/)?([a-z0-9]+)",
"url": "http://maxstream.video/\\1"
}
]
},

View File

@@ -1,99 +1,114 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------
# Conector MaxStream
# --------------------------------------------------------
import ast, sys
# sorry for obfuscation, it's for making a little more difficult for maxstream owner to counter this
import requests
from core import httptools, scrapertools, support
from lib import jsunpack
from platformcode import logger, config, platformtools
if sys.version_info[0] >= 3:
import urllib.parse as urlparse
else:
import urlparse
def test_video_exists(page_url):
logger.debug("(page_url='%s')" % page_url)
global data
if 'uprot/' in page_url:
id = httptools.downloadpage(page_url, follow_redirects=False, cloudscraper=True).headers.get('location').split('/')[-1]
else:
id = page_url.split('/')[-1]
page_url = requests.head('http://lozioangie.altervista.org/max_anticaptcha.php?id=' + id).headers.get('location')
data = httptools.downloadpage(page_url, cloudscraper=True).data
if scrapertools.find_single_match(data, '(?<!none);[^>]*>file was deleted'):
return False, config.get_localized_string(70449) % "MaxStream"
return True, ""
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
logger.debug("url=" + page_url)
video_urls = []
global data
if 'captcha' in data:
httptools.set_cookies(requests.get('http://lozioangie.altervista.org/maxcookie.php').json())
data = httptools.downloadpage(page_url, cloudscraper=True).data
# sitekey = scrapertools.find_multiple_matches(data, """data-sitekey=['"] *([^"']+)""")
# if sitekey: sitekey = sitekey[-1]
# captcha = platformtools.show_recaptcha(sitekey, page_url) if sitekey else ''
#
# possibleParam = scrapertools.find_multiple_matches(data,
# r"""<input.*?(?:name=["']([^'"]+).*?value=["']([^'"]*)['"]>|>)""")
# if possibleParam:
# post = {param[0]: param[1] for param in possibleParam if param[0]}
# if captcha: post['g-recaptcha-response'] = captcha
# if post:
# data = httptools.downloadpage(page_url, post=post, follow_redirects=True, verify=False).data
# else:
# platformtools.dialog_ok(config.get_localized_string(20000), config.get_localized_string(707434))
# return []
packed = support.match(data, patron=r"(eval\(function\(p,a,c,k,e,d\).*?)\s*</script").match
if packed:
data = jsunpack.unpack(packed)
url = scrapertools.find_single_match(data, 'src:\s*"([^"]+)')
if url:
video_urls.append(['m3u8 [MaxStream]', url])
return video_urls
# support.dbg()
# possible_cast_url = support.match('http://maxstream.video/?op=page&tmpl=../../download1', patron='<a[^<]+href="(?:https://maxstream\.video/)?([^".?]+/)"').matches
# for cast_url in possible_cast_url:
# data = httptools.downloadpage('http://maxstream.video/' + cast_url + page_url.split('/')[-1]).data
# url_video = ''
#
# lastIndexStart = data.rfind('<script>')
# lastIndexEnd = data.rfind('</script>')
#
# script = data[ (lastIndexStart + len('<script>')):lastIndexEnd ]
#
# match = scrapertools.find_single_match(script, r'(\[[^\]]+\])[^\{]*\{[^\(]+\(parseInt\(value\)\s?-\s?([0-9]+)')
# if match:
# char_codes = ast.literal_eval(match[0])
# hidden_js = "".join([chr(c - int(match[1])) for c in char_codes])
#
# newurl = scrapertools.find_single_match(hidden_js, r'\$.get\(\'([^\']+)').replace('https://', 'http://')
# castpage = httptools.downloadpage(newurl, headers={'x-requested-with': 'XMLHttpRequest', 'Referer': page_url}).data
# url_video = scrapertools.find_single_match(castpage, r"cc\.cast\('(http[s]?.[^']+)'")
# else:
# logger.debug('Something wrong: no url found before that :(')
#
# if url_video and url_video.split('/')[-1] == page_url.split('/')[-1]:
# import random, string
# parse = urlparse.urlparse(url_video)
# video_urls = [['mp4 [MaxStream]', url_video]]
# try:
# r1 = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(19))
# r2 = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(19))
# r3 = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(19))
# video_urls.append(['m3u8 [MaxStream]', '{}://{}/hls/{},{},{},{},.urlset/master.m3u8'.format(parse.scheme, parse.netloc, parse.path.split('/')[1], r1, r2, r3)])
# # video_urls.append(['m3u8 [MaxStream]', '{}://{}/hls/{},wpsc2hllm5g5fkjvslq,4jcc2hllm5gzykkkgha,fmca2hllm5jtpb7cj5q,.urlset/master.m3u8'.format(parse.scheme, parse.netloc, parse.path.split('/')[1])])
# except:
# logger.debug('Something wrong: Impossible get HLS stream')
# return video_urls
import ast ,sys
import base64
if sys .version_info [0 ]>=3 :
import urllib .parse as urlparse
else :
import urlparse
from core import httptools ,scrapertools ,support
from lib import jsunpack
from platformcode import logger ,config ,platformtools
def decode (OOO000OOOO0OOOOOO ):
OOO0OOOO0O00OOOOO =[]
OOO000000OOO000OO =__name__
OOO000OOOO0OOOOOO =base64 .urlsafe_b64decode (OOO000OOOO0OOOOOO ).decode ()
for OOO0OO0O000OOO0OO in range (len (OOO000OOOO0OOOOOO )):
OO0OOOO0O0O0OO000 =OOO000000OOO000OO [OOO0OO0O000OOO0OO %len (OOO000000OOO000OO )]
O0OO0O0O00OOOOOO0 =chr ((256 +ord (OOO000OOOO0OOOOOO [OOO0OO0O000OOO0OO ])-ord (OO0OOOO0O0O0OO000 ))%256 )
OOO0OOOO0O00OOOOO .append (O0OO0O0O00OOOOOO0 )
return "".join (OOO0OOOO0O00OOOOO )
headers ={decode ('w4jDmMOXw6jCksKzw5rCk8Obw5U='):decode ('w4DDlMOsw5_DkcOew5RdwqLCj8KowpPCnMK-w47Dj8Oiw6vCoMKSwrfDk8OWw6XCncOWw4XCmMKkwqXCm8KFwqLDncOjw5HDl8ONw4rDlMK-wpfDocKQwq3CpsKrwqDCmMKXwo3Cm8KwwrrDisKywr7Cn07DmcOKw6PDmMKUwrnDisOEw5jDosKOwpLCucONw6TDosKbw5LCkMKxwqTCosKiwpPClcKhwqrCl8KgwqfCl8KiwpN7w5zDg8Ohw5_DmcKSwrjDgsOTw5TDl8ObwqXCmsKlwqpcwqDClw==')}
def test_video_exists (page_url ):
logger .debug (page_url )
global data ,new_url
new_url =httptools .downloadpage (page_url ,follow_redirects =False ,cloudscraper =True ,alfa_s =True ).headers .get (decode ('w5_DlMOVw5fDmcObw6LCnA=='),page_url )
data =httptools .downloadpage (new_url ,cloudscraper =True ,alfa_s =True ).data
if scrapertools .find_single_match (data ,decode ('wpvCpMKuwpfDk8Ohw6HCk8KWwpzDk8ORwrLDj8KPwp_Dk8Ocw5HDl8KWw5zDk8OmTsORw4bDpMOYw6jDl8OJ')):
return False ,config .get_localized_string (70449 )%decode ('w4DDhsOqw4nDmcOkw5jCj8Oa')
return True ,""
def get_video_url (page_url ,premium =False ,user ="",password ="",video_password =""):
global data ,new_url ,code
page_url =new_url
logger .debug (decode ('w6jDl8OewrM=')+page_url )
code =scrapertools .find_single_match (data ,decode ('wpvCpMKsw57DmcOmw6PCocKnwpDCp8Ogw5XDqsOYw5XDn8OYw4bDn8Kkw5vDm8OXwpPDnMKQw5zCosOww6DDhsOOw5LCsMKHw5vDmsKHwpLDqcKPw5nDlsOdwrDClsKbwo3CvMOLwpXDgsKdwp8='))
O0OO00OOOOOOO00O0 =httptools .downloadpage (decode ('w5vDmcOmw6bDmMKswqJdw5rDgsOww6bDqMOkw4rDgsOawqHDm8Obw5rDisOhwqLCk8Oaw4PDncOXwqE=')+code +decode ('wqHDjcOmw6PDkQ=='),cloudscraper =True ,alfa_s =True ,headers =headers ).data
O0000000O00O0O000 =httptools .downloadpage (decode ('w5vDmcOmw6bDmMKswqJdw5rDgsOww6bDqMOkw4rDgsOawqHDm8Obw5rDisOhwqLCk8Oaw4PDncOXwqHDk8KYw5vDnsOnw5LCp8Oqw57DmcOmwpnCm8OJw6zDoMOg'),cloudscraper =True ,alfa_s =True ,headers =headers ).data
O0OOOOO0O0OOO00OO =scrapertools .find_multiple_matches (O0OO00OOOOOOO00O0 ,decode ('w5fDhsOmw5fCksOlw5zCosOSw4zDncOswrHDjcKMwoPDisKTwo_CmsORw4PClMKawovCmMKK'))
if O0OOOOO0O0OOO00OO :
O0OOOOO0O0OOO00OO =O0OOOOO0O0OOO00OO [-1 ]
OOOO00O00OO000O00 =platformtools .show_recaptcha (O0OOOOO0O0OOO00OO ,new_url )
if not OOOO00O00OO000O00 :
platformtools .play_canceled =True
return []
OOOO0O0O0000O00OO =scrapertools .find_multiple_matches (data ,decode ('wq_DjsOgw6bDmsOmwqFYwqzCicK3wq3DosOTw5LDhsKqw47Ch8KZw5PCjcONw5FVwo_CvsKjwpzCosKcwqTDl8OOw5_DmsOXwrPDgMKUwprCi8KVwrzDlsKawpbDj8KPworDiMKawofDj8K0w6HCsMKc'))
if OOOO0O0O0000O00OO :
O00OO000000000000 ={O0000O000OOOO00O0 [0 ]:O0000O000OOOO00O0 [1 ]for O0000O000OOOO00O0 in OOOO0O0O0000O00OO if O0000O000OOOO00O0 [0 ]}
if OOOO00O00OO000O00 :O00OO000000000000 [decode ('w5rCksOkw5vDiMOTw6PCosOQw4nDmcKgw6bDl8OYw5HDnMOhw5jDlw==')]=OOOO00O00OO000O00
if O00OO000000000000 :
data =httptools .downloadpage (page_url ,post =O00OO000000000000 ,follow_redirects =True ,cloudscraper =True ,alfa_s =True ,headers =headers ).data
else :
platformtools .dialog_ok (config .get_localized_string (20000 ),config .get_localized_string (707434 ))
return []
try :
O0O0O00000000OOOO =method_1 (O0OO00OOOOOOO00O0 )
O0OO000OO0000O00O =method_1 (O0000000O00O0O000 )
if not O0O0O00000000OOOO or O0O0O00000000OOOO [0 ][1 ].split (decode ('wp8='))[0 ]==O0OO000OO0000O00O [0 ][1 ].split (decode ('wp8='))[0 ]:
O0O0O00000000OOOO =method_1 (data )
O0OO000OO0000O00O =method_1 (httptools .downloadpage (decode ('w5vDmcOmw6bDmMKswqJdw5rDgsOww6bDqMOkw4rDgsOawqHDm8Obw5rDisOhwqLCj8Kgw5vDqcOnw6HCp8OZw5rDlMOmw5A='),cloudscraper =True ,alfa_s =True ).data )
except :
O0O0O00000000OOOO =[]
O0OO000OO0000O00O =[]
pass
if not O0O0O00000000OOOO or (O0OO000OO0000O00O and O0O0O00000000OOOO[0][1].split(decode ('wp8='))[0] == O0OO000OO0000O00O[0][1].split(decode ('wp8='))[0]):
O0O0O00000000OOOO =method_2 ()
if not O0O0O00000000OOOO :
O0O0O00000000OOOO =method_3 ()
return O0O0O00000000OOOO
def method_1 (O0O000OO00OOO0OOO ):
OOOOO0O00OOOO0OOO =support .match (O0O000OO00OOO0OOO ,patron =decode ('wpvDisOow5fDkcOOwpvClMOiw4_Dm8Onw53DocOTwr3ClcOjwpHDk8Kiw4jCnsOeWsOSwo3DnMOPwp3CoMKPwqDClsOPw5jCnMKywpTDpcOWwqDDlsORw6w=')).matches
if OOOOO0O00OOOO0OOO and len (OOOOO0O00OOOO0OOO )==1 :
O0O0O00O00OO0O0O0 =jsunpack .unpack (OOOOO0O00OOOO0OOO [0 ])
O0OOO0OOO00O000OO =scrapertools .find_single_match (O0O0O00O00OO0O0O0 ,decode ('w6bDl8OVwrDDgcOlwp1QwpXCvMOWwpXDkcKdwo4='))
if O0OOO0OOO00O000OO :
return [[decode ('w6DCmMOnwq7ChcONw4DCj8OlwrTDrMOlw5nDk8OSwr4='),O0OOO0OOO00O000OO ]]
return []
def method_2 ():
global data ,new_url ,code
O000O0O000O0OOO0O =support .match (data ,patron =decode ('wq_DhsONw5TCocOPwp7ClsOfw4bDnsKwwpbCmsKkwpvDlcOnw5nDosOpwp_CocKiwpvDjsOZw6vDp8Omw5fDhsOOw4nCocObw5vDmsOKw6HColfCrMKJw5PDkcKWwqDCpMK-wpjCosKO')).matches
for OOOO0O00OO0OOO00O in O000O0O000O0OOO0O :
if OOOO0O00OO0OOO00O ==decode ('w5vDmcOmw6bDmMKswqJd'):
continue
data =httptools .downloadpage (decode ('w5vDmcOmw6bCn8KhwqLCm8OOw5nDq8Onw6bDl8OGw47Cm8Opw47DlsObw5TCoQ==')+OOOO0O00OO0OOO00O +code ,cloudscraper =True ,alfa_s =True ).data
OO00OO00O00000O0O =""
O0OOO0O00O0000O00 =data .rfind (decode ('wq_DmMOVw6jDjsOiw6ds'))
O00OO0O0000O000OO =data .rfind (decode ('wq_ClMOlw5nDl8Obw6PCosKr'))
O000OOO0O0000OOO0 =data [(O0OOO0O00O0000O00 +len (decode ('wq_DmMOVw6jDjsOiw6ds'))):O00OO0O0000O000OO ]
O0O0O000O00O0OOO0 =scrapertools .find_single_match (O000OOO0O0000OOO0 ,decode ('wpvDgcONw5HDg8OOw5DCi8KYwr3DlcKcw4_DkMOBw5zDisKdw4HDrcORw4PDjsKbwovCmMK9wqDDo8OVw6TDmMOGwrbDocOZw47CnsObw5PDn8Kjw5LCvcKhw4_Dp8KxwpLCvcOgwrLCjcONwqbCksKrw5BZwpY='))
if O0O0O000O00O0OOO0 :
O000OOOO0OO0OO000 =ast .literal_eval (O0O0O000O00O0OOO0 [0 ])
O0O0O0OO0O00O00OO ="".join ([chr (OO000O00OOO0O00O0 -int (O0O0O000O00O0OOO0 [1 ]))for OO000O00OOO0O00O0 in O000OOOO0OO0OO000 ])
O0OOOO0O00O00OOO0 =scrapertools .find_single_match (O0O0O0OO0O00O00OO ,decode ('w4_CicKgw53DisOmw49Ww4nCiMKgw47DksOOwozCvsKYwpw=')).replace (decode ('w5vDmcOmw6bDmMKswqJd'),decode ('w5vDmcOmw6bCn8KhwqI='))
OO0O00OOOO0OO0O00 =httptools .downloadpage (O0OOOO0O00O00OOO0 ,headers ={decode ('w6vCksOkw5vDlsOnw5jCocOhw4bDnMKgw6vDm8OZw4k='):decode ('w4vCssK-wr7DmcOmw6PCgMOSw5LDrcOYw6fDpg=='),decode ('w4XDisOYw5vDl8OXw6U='):new_url },cloudscraper =True ,alfa_s =True ).data
OO00OO00O00000O0O =scrapertools .find_single_match (OO0O00OOOO0OO0O00 ,decode ('w5bDiMOOwqTDiMOTw6bCosOJwonCn8Kbw5zDpsOZw5HDiMOmw4LCscKkw4DDkMKawovCmMKKwp8='))
else :
logger .debug (decode ('w4bDlMOfw5vDmcOaw5zCnMOUwoHDr8Olw6PDoMOMwpvCjcOhw5TCksOrw5fDnsKTwpTDnMOWw6bDl8KUw5TDisOHw5zDpcOKwpLDqsONw5PDp07Cp8KJ'))
if OO00OO00O00000O0O and OO00OO00O00000O0O .split (decode ('wqI='))[-1 ]==code :
import random ,string
O0000000O00OOOOO0 =urlparse .urlparse (OO00OO00O00000O0O )
O00O0O0O0OOOO0O0O =[[decode ('w6DDlcKmwpbDgMK_w5TCpsOAw5XDqsOYw5XDn8OC'),OO00OO00O00000O0O ]]
try :
O000O000O0O000OO0 ="".join (random .choice (string .ascii_letters +string .digits )for O00OO0O000O0OO0OO in range (19 ))
OOO000O0O0O000OO0 ="".join (random .choice (string .ascii_letters +string .digits )for O0OO000O0OO0O0000 in range (19 ))
O0O0O0O000O0OO00O ="".join (random .choice (string .ascii_letters +string .digits )for OOOOOOO00OOOO0O0O in range (19 ))
O00O0O0O0OOOO0O0O .append ([decode ('w6DCmMOnwq7ChcONw4DCj8OlwrTDrMOlw5nDk8OSwr4='),decode ('w67DosKswqXClMOtw7Bdw5XDjcOrwqLDr8OvwpHDnMOqwp_DoMOvwqLDoMOvwp9cw6LDk8Okw6bDmcOmwpTDjsOOw6bDmcOXw6jCk8OfwqbCo8Kl').format (O0000000O00OOOOO0 .scheme ,O0000000O00OOOOO0 .netloc ,O0000000O00OOOOO0 .path .split (decode ('wqI='))[1 ],O000O000O0O000OO0 ,OOO000O0O0O000OO0 ,O0O0O0O000O0OO00O )])
except :
logger .debug (decode ('w4bDlMOfw5vDmcOaw5zCnMOUwoHDr8Olw6PDoMOMwpvCjcK8w5LDosOlw5jDpcOcwpDDmcOGwpjDmsOZw6bChcKpwrnDhsKFw6XDqsOXw5fDlMKb'))
return []
def method_3 ():
httptools .set_cookies ({decode ('w6HDhsOfw5s='):decode ('w5fDnA=='),decode ('w6nDhsOew6vDig=='):decode ('wqQ='),decode ('w5fDlMOfw5fDjsOg'):decode ('w6DDhsOqw6nDmcOkw5jCj8Oawo_DrsOcw5jDl8OU')},False )
OOOO0OOOO0000OOO0 =httptools .downloadpage (decode ('w5vDmcOmw6bDmMKswqJdw5rDgsOww6bDqMOkw4rDgsOawqHDm8Obw5rDisOhwqLCksOcw5jDpsOfw6PDk8OJwpDDlsOhw5jDm8Oaw4rDmMOlwpPDksOZwqbDo8Ocw6LCpMOJw6bDn8Ki')+base64 .b64encode (decode ('wqjClcKiw7LDoMOv').format (code ).encode ()).decode (),timeout =10 ,cloudscraper =True ,alfa_s =True ,headers ={decode ('w4vCksOEw5vDlsOnw5jCocOhw4bDnMKgw4vDm8OZw4k='):decode ('w4vCssK-wr7DmcOmw6PCgMOSw5LDrcOYw6fDpg=='),decode ('w4XDisOYw5vDl8OXw6U='):decode ('w5vDmcOmw6bDmMKswqJdw5rDgsOww6bDqMOkw4rDgsOawqHDm8Obw5rDisOhwqLCksKc')+code }).data
OO0OOO000O0O000O0 =scrapertools .find_single_match (OOOO0OOOO0000OOO0 ,decode ('wrfDlMOpw6TDkcOhw5TCksKNwqnDocOaw5zCksKpw4bDk8Ocw5PDm8Oqw47DocOhXMKXwqDCtMOUwpTDmsOXw4bDk8KwwozCmsORw4PCmcOQWcKW'))
if OO0OOO000O0O000O0 :
return [[decode ('w6DDlcKmwpbDgMK_w5TCpsOAw5XDqsOYw5XDn8OC'),OO0OOO000O0O000O0 ]]
return []

View File

@@ -4,9 +4,9 @@ import re, os, sys, time, requests, xbmc, xbmcaddon
from core import filetools, httptools, jsontools
from core.support import info, match
from platformcode import config, platformtools
from platformcode import config, platformtools, logger
from lib.guessit import guessit
from torrentool.api import Torrent
from lib.torrentool.api import Torrent
if sys.version_info[0] >= 3:
import urllib.parse as urllib
@@ -92,13 +92,15 @@ def elementum_download(item):
while not filetools.isfile(filetools.join(elementum_setting.getSetting('torrents_path'), TorrentName + '.torrent')):
time.sleep(1)
monitor_update(TorrentPath, TorrentName)
monitor_update(TorrentPath, TorrentName)
def elementum_monitor():
# from core.support import dbg;dbg()
path = xbmc.translatePath(config.get_setting('downloadlistpath'))
elementum_setting, elementum_host, TorrentPath = setting()
# active_torrent = filetools.listdir(TorrentPath)
# logger.debug('ELEMENTUM:', elementum_setting, elementum_host, TorrentPath)
if elementum_setting:
# check if command file exist
@@ -113,10 +115,11 @@ def elementum_monitor():
if len(Monitor) > 0:
try:
data = requests.get(elementum_host + '/list').json()
data = requests.get(elementum_host + '/list', timeout=2).json()
except:
data = ''
if data:
# from core.support import dbg;dbg()
for it in data:
progress = round(it['progress'], 2)
status = it['status']
@@ -143,6 +146,7 @@ def monitor_update(TorrentPath, value, remove=False):
json = jsontools.load(open(monitor, "r").read())
Monitor = json['monitor']
info = Torrent.from_file(filetools.join(TorrentPath, value + '.torrent'))
logger.debug('ELEMENTUM MONITOR', Monitor)
path = xbmc.translatePath(config.get_setting('downloadlistpath'))
if not value in Monitor:
@@ -174,13 +178,14 @@ def set_elementum(SET=False):
backup_setting = json['settings']
write = False
if SET:
elementum_setting.setSetting('download_storage', '0')
if elementum_setting.getSetting('logger_silent') == False or not 'logger_silent' in backup_setting:
elementum_setting.setSetting('logger_silent', 'true')
backup_setting['logger_silent'] = 'false'
if elementum_setting.getSetting('download_storage') != 0 or not 'download_storage' in backup_setting:
backup_setting['download_storage'] = elementum_setting.getSetting('download_storage') # Backup Setting
elementum_setting.setSetting('download_storage', '0') # Set Setting
# if elementum_setting.getSetting('download_storage') != 0 or not 'download_storage' in backup_setting:
# backup_setting['download_storage'] = elementum_setting.getSetting('download_storage') # Backup Setting
# elementum_setting.setSetting('download_storage', '0') # Set Setting
if elementum_setting.getSetting('download_path') != config.get_setting('downloadpath') or not 'download_path' in backup_setting:
backup_setting['download_path'] = elementum_setting.getSetting('download_path') # Backup Setting
@@ -189,7 +194,8 @@ def set_elementum(SET=False):
elif backup_setting:
elementum_setting.setSetting('logger_silent', backup_setting['logger_silent'])
elementum_setting.setSetting('download_storage', backup_setting['download_storage'])
elementum_setting.setSetting('download_storage', '1')
# elementum_setting.setSetting('download_storage', backup_setting['download_storage'])
elementum_setting.setSetting('download_path', backup_setting['download_path'])
json['settings'] = {}
write = True

View File

@@ -400,7 +400,16 @@ class AddonMonitor(xbmc.Monitor):
self.settings_pre = config.get_all_settings_addon()
def onNotification(self, sender, method, data):
if method == 'VideoLibrary.OnUpdate':
# logger.debug('METHOD', method, sender, data)
if method == 'Playlist.OnAdd':
from core import db
db['OnPlay']['addon'] = True
db.close()
elif method == 'Player.OnStop':
from core import db
db['OnPlay']['addon'] = False
db.close()
elif method == 'VideoLibrary.OnUpdate':
xbmc_videolibrary.set_watched_on_kod(data)
logger.debug('AGGIORNO')
@@ -538,7 +547,7 @@ if __name__ == "__main__":
break
if monitor.waitForAbort(1): # every second
join_threads()
# db need to be closed when not used, it will cause freezes
db.close()
join_threads()
break

View File

@@ -8,6 +8,7 @@ from core import channeltools, servertools, scrapertools
from platformcode import platformtools, config, logger
from platformcode.launcher import run
from threading import Thread
from specials.search import save_search
if sys.version_info[0] >= 3:
PY3 = True
@@ -28,10 +29,12 @@ def busy(state):
if state: xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
else: xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
def set_workers():
workers = config.get_setting('thread_number') if config.get_setting('thread_number') > 0 else None
return workers
def Search(*args):
xbmc.executebuiltin('Dialog.Close(all)')
w = SearchWindow('GlobalSearch.xml', config.get_runtime_path())
@@ -119,9 +122,12 @@ class SearchWindow(xbmcgui.WindowXML):
if not self.item.text: self.item.text = platformtools.dialog_input(default=last_search, heading='')
if self.item.text:
channeltools.set_channel_setting('Last_searched', self.item.text, 'search')
from specials.search import save_search
save_search(self.item.text)
if self.item.mode == 'all':
save_search(self.item.text)
else:
if self.item.context:
del self.item.context # needed for preventing same content twice in saved search
save_search(self.item.__dict__)
def getActionsThread(self):
logger.debug()
@@ -186,7 +192,7 @@ class SearchWindow(xbmcgui.WindowXML):
rating = str(result.get('vote_average', ''))
new_item = Item(channel='globalsearch',
action=True,
action="Search",
title=title,
thumbnail=thumbnail,
fanart=fanart,
@@ -246,8 +252,8 @@ class SearchWindow(xbmcgui.WindowXML):
discovery = {'url': 'person/%s/combined_credits' % cast_id, 'page': '1', 'sort_by': 'primary_release_date.desc', 'language': def_lang}
self.persons.append(discovery)
new_item = Item(channel='search',
action=True,
new_item = Item(channel='globalsearch',
action="Search",
title=name,
thumbnail=thumb,
mode='search')
@@ -641,7 +647,7 @@ class SearchWindow(xbmcgui.WindowXML):
self.page -= 1
self.actors()
elif search == 'persons':
item = self.item.clone(mode='person_', discovery=self.persons[pos])
item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')).clone(mode='person_', discovery=self.persons[pos], text=True, folder=False)
Search(item, self.thActions)
if close_action:
self.close()
@@ -649,7 +655,9 @@ class SearchWindow(xbmcgui.WindowXML):
item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item'))
if self.item.mode == 'movie': item.contentTitle = self.RESULTS.getSelectedItem().getLabel()
else: item.contentSerieName = self.RESULTS.getSelectedItem().getLabel()
item.folder = False
logger.debug(item)
Search(item, self.thActions)
if close_action:
self.close()
@@ -790,7 +798,6 @@ class SearchWindow(xbmcgui.WindowXML):
busy(False)
self.close()
def context(self):
focus = self.getFocusId()
if focus == EPISODESLIST: # context on episode

View File

@@ -77,21 +77,9 @@ def sub_menu(item):
def saved_search(item):
logger.debug()
itemlist = list()
saved_searches_list = get_saved_searches()
itemlist = get_saved_searches()
for saved_search_text in saved_searches_list:
itemlist.append(
Item(channel=item.channel if not config.get_setting('new_search') else 'globalsearch',
action="new_search" if not config.get_setting('new_search') else 'Search',
title=typo(saved_search_text.split('{}')[0], 'bold'),
search_text=saved_search_text.split('{}')[0],
text=saved_search_text.split('{}')[0],
mode='all',
thumbnail=get_thumb('search.png')))
if len(saved_searches_list) > 0:
if len(itemlist) > 0:
itemlist.append(
Item(channel=item.channel,
action="clear_saved_searches",
@@ -807,11 +795,9 @@ def save_search(text):
if text:
saved_searches_limit = config.get_setting("saved_searches_limit")
current_saved_searches_list = config.get_setting("saved_searches_list", "search")
if current_saved_searches_list is None:
saved_searches_list = config.get_setting("saved_searches_list", "search")
if not saved_searches_list:
saved_searches_list = []
else:
saved_searches_list = list(current_saved_searches_list)
if text in saved_searches_list:
saved_searches_list.remove(text)
@@ -824,13 +810,25 @@ def save_search(text):
def clear_saved_searches(item):
config.set_setting("saved_searches_list", list(), "search")
platformtools.dialog_ok(config.get_localized_string(60423), config.get_localized_string(60424))
platformtools.itemlist_refresh()
def get_saved_searches():
current_saved_searches_list = config.get_setting("saved_searches_list", "search")
if current_saved_searches_list is None:
saved_searches_list = []
else:
saved_searches_list = list(current_saved_searches_list)
if not current_saved_searches_list:
current_saved_searches_list = []
saved_searches_list = []
for saved_search_item in current_saved_searches_list:
if type(saved_search_item) == str:
saved_searches_list.append(
Item(channel='search' if not config.get_setting('new_search') else 'globalsearch',
action="new_search" if not config.get_setting('new_search') else 'Search',
title=typo(saved_search_item.split('{}')[0], 'bold'),
search_text=saved_search_item.split('{}')[0],
text=saved_search_item.split('{}')[0],
mode='all',
thumbnail=get_thumb('search.png')))
else:
saved_searches_list.append(Item().fromjson(json.dumps(saved_search_item)))
return saved_searches_list

View File

@@ -485,7 +485,8 @@ def findvideos(item):
except ImportError: from urlparse import urlsplit
try:
if urlsplit(item_json.url).netloc.split('.')[0] in channel.host:
item_json.url = channel.host + urlsplit(item_json.url).path
spurl = urlsplit(item_json.url)
item_json.url = channel.host + spurl.path + ('?' + spurl.query if spurl.query else '')
except: pass
try: