recaptcha librerie
This commit is contained in:
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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('<')
|
||||||
@@ -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"
|
||||||
@@ -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 /"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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)')
|
||||||
@@ -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({})
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,613 @@
|
|||||||
|
# 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__en.js
|
||||||
|
"""[:-1]
|
||||||
|
|
||||||
|
STRINGS_VERSION = "0.1.0"
|
||||||
|
STRINGS_PATH = os.path.join(
|
||||||
|
os.path.expanduser("~"), ".cache", "librecaptcha", "cached-strings",
|
||||||
|
)
|
||||||
|
|
||||||
|
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) -> List[str]:
|
||||||
|
def get_json():
|
||||||
|
with open(STRINGS_PATH) 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),
|
||||||
|
path=STRINGS_PATH,
|
||||||
|
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):
|
||||||
|
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
|
||||||
|
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.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"\bselect all\b", 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)
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user