KoD 1.7.3
- fix vari\n\n
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ custom_code.json
|
||||
last_commit.txt
|
||||
__pycache__/
|
||||
.vscode/settings.json
|
||||
bin/
|
||||
lib/abi
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -140,6 +140,7 @@ def peliculas(item):
|
||||
|
||||
@support.scrape
|
||||
def episodios(item):
|
||||
registerOrLogin()
|
||||
logger.debug(item)
|
||||
# debug = True
|
||||
data = item.data
|
||||
|
||||
11
channels/streamingita.json
Normal file
11
channels/streamingita.json
Normal 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
74
channels/streamingita.py
Normal 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()
|
||||
@@ -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")
|
||||
|
||||
@@ -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
29
lib/esprima/__init__.py
Normal 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
105
lib/esprima/__main__.py
Normal 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
125
lib/esprima/character.py
Normal 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
|
||||
176
lib/esprima/comment_handler.py
Normal file
176
lib/esprima/comment_handler.py
Normal 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
72
lib/esprima/compat.py
Normal 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
|
||||
74
lib/esprima/error_handler.py
Normal file
74
lib/esprima/error_handler.py
Normal 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
125
lib/esprima/esprima.py
Normal 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
100
lib/esprima/jsx_nodes.py
Normal 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
584
lib/esprima/jsx_parser.py
Normal 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. '{'
|
||||
numeric = (ch == '#')
|
||||
elif length == 3:
|
||||
if numeric:
|
||||
# e.g. 'A'
|
||||
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. 'A' 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
38
lib/esprima/jsx_syntax.py
Normal 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
90
lib/esprima/messages.py
Normal 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
620
lib/esprima/nodes.py
Normal 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
46
lib/esprima/objects.py
Normal 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
3104
lib/esprima/parser.py
Normal file
File diff suppressed because it is too large
Load Diff
1189
lib/esprima/scanner.py
Normal file
1189
lib/esprima/scanner.py
Normal file
File diff suppressed because it is too large
Load Diff
100
lib/esprima/syntax.py
Normal file
100
lib/esprima/syntax.py
Normal 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
50
lib/esprima/token.py
Normal 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
193
lib/esprima/tokenizer.py
Normal 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
40
lib/esprima/utils.py
Normal 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
288
lib/esprima/visitor.py
Normal 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({})
|
||||
281
lib/esprima/xhtml_entities.py
Normal file
281
lib/esprima/xhtml_entities.py
Normal 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",
|
||||
}
|
||||
23
lib/librecaptcha/__init__.py
Normal file
23
lib/librecaptcha/__init__.py
Normal 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
|
||||
280
lib/librecaptcha/__main__.py
Normal file
280
lib/librecaptcha/__main__.py
Normal 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
317
lib/librecaptcha/cli.py
Normal 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
100
lib/librecaptcha/errors.py
Normal 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
|
||||
113
lib/librecaptcha/extract_strings.py
Normal file
113
lib/librecaptcha/extract_strings.py
Normal 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
787
lib/librecaptcha/gui.py
Normal 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
|
||||
)
|
||||
84
lib/librecaptcha/librecaptcha.py
Normal file
84
lib/librecaptcha/librecaptcha.py
Normal 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
|
||||
621
lib/librecaptcha/recaptcha.py
Normal file
621
lib/librecaptcha/recaptcha.py
Normal 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)
|
||||
40
lib/librecaptcha/typing.py
Normal file
40
lib/librecaptcha/typing.py
Normal 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
|
||||
36
lib/librecaptcha/user_agent_data.py
Normal file
36
lib/librecaptcha/user_agent_data.py
Normal 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"
|
||||
]
|
||||
23
lib/librecaptcha/user_agents.py
Normal file
23
lib/librecaptcha/user_agents.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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=[]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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.
@@ -4857,7 +4857,7 @@ msgid "Insert page number"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#70514"
|
||||
msgid ""
|
||||
msgid "Notify current view saves"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#70515"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
BIN
resources/skins/Default/media/selection.png
Normal file
BIN
resources/skins/Default/media/selection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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
|
||||
|
||||
13
service.py
13
service.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user