├── MANIFEST.in ├── .travis.yml ├── requirements.txt ├── nonocaptcha ├── data │ ├── deface.html │ └── override.js ├── nonocaptcha.example.yaml ├── __init__.py ├── exceptions.py ├── proxy.py ├── util.py ├── base.py ├── launcher.py ├── audio.py ├── image.py ├── speech.py └── solver.py ├── TODO.md ├── examples ├── demo.py ├── basic.py ├── console.py └── app.py ├── .gitignore ├── setup.py ├── Dockerfile ├── README.rst ├── CHANGES.md └── LICENSE.rst /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include nonocaptcha/data/*.* 2 | include nonocaptcha/nonocaptcha.example.yaml 3 | include README.rst 4 | include setup.py 5 | include CHANGES.md -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3.6 3 | sudo: required 4 | before_install: 5 | - sudo apt-get update && sudo apt-get install swig libpulse-dev libasound2-dev 6 | install: 7 | - pip install -r requirements.txt 8 | script: 9 | - flake8 --exclude venv 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiobotocore==0.9.3 2 | aiofiles==0.3.2 3 | aiohttp==3.3.2 4 | async-timeout==3.0.0 5 | peewee==3.6.4 6 | pillow==5.2.0 7 | pocketsphinx==0.1.15 8 | psutil==5.4.6 9 | pydub==0.22.1 10 | pyppeteer==0.0.24 11 | pyyaml==3.13 12 | requests==2.20.0 13 | user-agent==0.1.9 14 | websockets==6.0 15 | flake8==3.5.0 -------------------------------------------------------------------------------- /nonocaptcha/data/deface.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | nonoCAPTCHA 4 | 5 | 6 | 7 |
8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ======= 3 | 1. Define parameters that are interchangeable and list them along with explanations to the README. 4 | 1. Output version number when script starts 5 | 1. Add logging for displaying error messages during runtime, and option to output to file 6 | 1. Ensure script can run auto-piloted, without user interaction and safely integrating with Jetson TX2 7 | 1. Build an API for customizing the eye, including but not limited to color, shape, focus. This should have a separate option for developers with flexible yet breakable settings control. 8 | 1. Brainstorm. -------------------------------------------------------------------------------- /examples/demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from nonocaptcha.solver import Solver 3 | 4 | pageurl = "https://www.google.com/recaptcha/api2/demo" 5 | sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" 6 | 7 | proxy = "127.0.0.1:1000" 8 | auth_details = {"username": "user", "password": "pass"} 9 | args = ["--timeout 5"] 10 | options = {"ignoreHTTPSErrors": True, "args": args} 11 | client = Solver( 12 | pageurl, sitekey, options=options, proxy=proxy, proxy_auth=auth_details 13 | ) 14 | 15 | solution = asyncio.get_event_loop().run_until_complete(client.start()) 16 | if solution: 17 | print(solution) 18 | -------------------------------------------------------------------------------- /nonocaptcha/nonocaptcha.example.yaml: -------------------------------------------------------------------------------- 1 | debug: true 2 | headless: false 3 | keyboard_traverse: false 4 | block_images: true 5 | timeout: 6 | page_load: 30 7 | deface: 30 8 | animation: 5 9 | 10 | speech: 11 | service: pocketsphinx 12 | deepspeech: 13 | model_dir: deepspeech/models 14 | pocketsphinx: 15 | model_dir: pocketsphinx/model 16 | azure: 17 | api_subkey: 18 | amazon: 19 | secret_key_id: 20 | secret_access_key: 21 | region: 22 | s3_bucket: 23 | 24 | data: 25 | deface_html: data/deface.html 26 | jquery_js: data/jquery.js 27 | override_js: data/override.js 28 | pictures: data/pictures 29 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | from nonocaptcha.solver import Solver 5 | 6 | if len(sys.argv) == 4: 7 | pageurl, sitekey, proxy = sys.argv[1:] 8 | else: 9 | print('Invalid number of arguments (pageurl, sitekey, proxy)') 10 | sys.exit(0) 11 | 12 | 13 | loop = asyncio.get_event_loop() 14 | options = { 15 | "headless": False, 16 | "ignoreHTTPSErrors": True, 17 | "args": ["--timeout 5"]} 18 | if proxy.lower() == "none": 19 | proxy = None 20 | client = Solver(pageurl, sitekey, options=options, proxy=proxy) 21 | try: 22 | result = loop.run_until_complete(client.start()) 23 | except asyncio.CancelledError: 24 | raise 25 | else: 26 | if result: 27 | print(result) 28 | -------------------------------------------------------------------------------- /nonocaptcha/data/override.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | window.ready_eddy = false; 3 | 4 | document.addEventListener('DOMContentLoaded', waitondom, false); 5 | window.RTCPeerConnection = undefined; 6 | window.webkitRTCPeerConnection = undefined; 7 | var waitondom = function () { 8 | for (let frame of window.document.querySelectorAll('iframe')){ 9 | if (frame.contentWindow !== "undefined") { 10 | for (const key of Object.keys(_navigator)) { 11 | obj = frame.contentWindow.navigator; 12 | Object.defineProperty(obj, key, { 13 | value: _navigator[key] 14 | }); 15 | } 16 | } 17 | } 18 | } 19 | 20 | for (const key of Object.keys(_navigator)) { 21 | obj = window.navigator; 22 | Object.defineProperty(obj, key, { 23 | value: _navigator[key] 24 | }); 25 | } -------------------------------------------------------------------------------- /nonocaptcha/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import sys 6 | 7 | version_info = (1, 8, 8) 8 | __version__ = "{}.{}.{}".format(*version_info) 9 | 10 | 11 | authors = (("Michael Mooney", "mikeyy@mikeyy.com"),) 12 | 13 | authors_email = ", ".join("{}".format(email) for _, email in authors) 14 | 15 | __license__ = "GPL-3.0" 16 | __author__ = ", ".join( 17 | "{} <{}>".format(name, email) for name, email in authors 18 | ) 19 | 20 | package_info = ( 21 | "An asynchronized Python library to automate solving ReCAPTCHA v2 by audio" 22 | ) 23 | __maintainer__ = __author__ 24 | 25 | __all__ = ( 26 | "__author__", 27 | "__author__", 28 | "__license__", 29 | "__maintainer__", 30 | "__version__", 31 | "version_info", 32 | "package_dir", 33 | "package_info", 34 | ) 35 | 36 | sys.path.append(os.getcwd()) 37 | package_dir = os.path.dirname(os.path.abspath(__file__)) 38 | -------------------------------------------------------------------------------- /nonocaptcha/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Exceptions used in library. """ 5 | 6 | 7 | class nonocaptchaError(Exception): 8 | """ nonoCAPTCHA base exception. """ 9 | 10 | 11 | class SafePassage(nonocaptchaError): 12 | """ Raised when all checks have passed. Such as being detected or try 13 | again. 14 | """ 15 | pass 16 | 17 | 18 | class TryAgain(nonocaptchaError): 19 | """ Raised when audio deciphering is incorrect and we can try again. """ 20 | pass 21 | 22 | 23 | class ReloadError(nonocaptchaError): 24 | """ Raised when audio file doesn't reload to a new one. """ 25 | pass 26 | 27 | 28 | class DownloadError(nonocaptchaError): 29 | """ Raised when downloading the audio file errors. """ 30 | pass 31 | 32 | 33 | class ButtonError(nonocaptchaError): 34 | """ Raised when a button doesn't appear. """ 35 | pass 36 | 37 | 38 | class DefaceError(nonocaptchaError): 39 | """ Raised when defacing page times out. """ 40 | pass 41 | 42 | 43 | class PageError(nonocaptchaError): 44 | """ Raised when loading page times out. """ 45 | pass 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Rope 43 | .ropeproject 44 | 45 | # Django stuff: 46 | *.log 47 | *.pot 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | # Stash directory 53 | .stash/ 54 | 55 | # Symlinks 56 | pyppeteer 57 | aiomisc 58 | aiohttp 59 | 60 | # Config file 61 | nonocaptcha.yaml 62 | 63 | # Mac's DS_Store 64 | .DS_Store 65 | 66 | # Modified Pyppeteer files 67 | .pyppeteer 68 | 69 | # build script 70 | build.sh 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # proxy database 76 | proxy.db 77 | proxy.db-shm 78 | proxy.db-wal 79 | proxies.txt 80 | 81 | # speech-to-text module folders 82 | deepspeech 83 | pocketsphinx 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | from importlib.machinery import SourceFileLoader 4 | 5 | 6 | module_name = "nonocaptcha" 7 | 8 | module = SourceFileLoader( 9 | module_name, os.path.join(module_name, "__init__.py") 10 | ).load_module() 11 | 12 | 13 | def load_requirements(fname): 14 | """ load requirements from a pip requirements file """ 15 | with open(fname) as f: 16 | line_iter = (line.strip() for line in f.readlines()) 17 | return [line for line in line_iter if line and line[0] != "#"] 18 | 19 | 20 | setup( 21 | name=module_name.replace("_", "-"), 22 | version=module.__version__, 23 | author=module.__author__, 24 | author_email=module.authors_email, 25 | license=module.__license__, 26 | description=module.package_info, 27 | url="https://github.com/mikeyy/nonoCAPTCHA", 28 | long_description=open("README.rst").read(), 29 | platforms="all", 30 | classifiers=[ 31 | "Development Status :: 4 - Beta", 32 | "Intended Audience :: Developers", 33 | "Intended Audience :: Science/Research", 34 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 35 | "Natural Language :: English", 36 | "Operating System :: OS Independent", 37 | "Programming Language :: Python :: 3.6", 38 | "Programming Language :: Python :: 3.7", 39 | "Programming Language :: JavaScript", 40 | "Topic :: Scientific/Engineering", 41 | "Topic :: Software Development :: Libraries", 42 | "Topic :: Utilities" 43 | ], 44 | package_data={'data': ['*.*']}, 45 | include_package_data=True, 46 | packages=find_packages(), 47 | install_requires=load_requirements("requirements.txt"), 48 | ) 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the Docker container by running "docker build ." in the same folder as 2 | # Dockerfile. After the build is complete, run "docker images" and copy the 3 | # most recent create image. Then run the command "docker run -i -t COPIEDIMAGE" 4 | # which will place you in the shell of the newly created container. 5 | # All files are located in /nonocaptcha. 6 | 7 | 8 | # This Dockerfile assumes all required files/folders are in the relative 9 | # folder: 10 | # - nonocaptcha.yaml 11 | # - examples/app.py 12 | # - pocketsphinx (folder) 13 | # You may want to add proxies.txt at the bottom of this file. 14 | 15 | # We are using Ubuntu 16.04 for the base Docker image 16 | FROM ubuntu:16.04 17 | 18 | # This installs all the required packages for Python3.6, Chrome, and 19 | # Pocketsphinx 20 | RUN apt-get update \ 21 | && apt-get install -y \ 22 | libpangocairo-1.0-0 \ 23 | libx11-xcb1 \ 24 | libxcomposite1 \ 25 | libxcursor1 \ 26 | libxdamage1 \ 27 | libxi6 \ 28 | libxtst6 \ 29 | libnss3 \ 30 | libcups2 \ 31 | libxss1 \ 32 | libxrandr2 \ 33 | libgconf-2-4 \ 34 | libasound2 \ 35 | libasound2-dev \ 36 | libatk1.0-0 \ 37 | libgtk-3-0 \ 38 | gconf-service \ 39 | libappindicator1 \ 40 | libc6 \ 41 | libcairo2 \ 42 | libcups2 \ 43 | libdbus-1-3 \ 44 | libexpat1 \ 45 | libfontconfig1 \ 46 | libgcc1 \ 47 | libgdk-pixbuf2.0-0 \ 48 | libglib2.0-0 \ 49 | libnspr4 \ 50 | libpango-1.0-0 \ 51 | libpulse-dev \ 52 | libstdc++6 \ 53 | libx11-6 \ 54 | libxcb1 \ 55 | libxext6 \ 56 | libxfixes3 \ 57 | libxrender1 \ 58 | libxtst6 \ 59 | ca-certificates \ 60 | fonts-liberation \ 61 | lsb-release \ 62 | xdg-utils \ 63 | build-essential \ 64 | ffmpeg \ 65 | swig \ 66 | software-properties-common curl \ 67 | && add-apt-repository ppa:jonathonf/python-3.6 \ 68 | && apt-get remove -y software-properties-common \ 69 | && apt autoremove -y \ 70 | && apt-get update \ 71 | && apt-get install -y python3.6 \ 72 | python3.6-dev \ 73 | && curl -o /tmp/get-pip.py "https://bootstrap.pypa.io/get-pip.py" \ 74 | && python3.6 /tmp/get-pip.py \ 75 | && apt-get remove -y curl \ 76 | && apt autoremove -y \ 77 | && pip install nonocaptcha 78 | 79 | # Copies required files for running nonoCAPTCHA to the Docker container. 80 | # You can comment out pocketsphinx if you aren't using Pocketsphinx. 81 | RUN mkdir /nonocaptcha 82 | ADD pocketsphinx /nonocaptcha/pocketsphinx 83 | ADD nonocaptcha.yaml /nonocaptcha 84 | # ADD proxies.txt /nonocaptcha/proxies.txt 85 | 86 | # This determines which file you want to copy over to the Docker container, 87 | # by default the aiohttp server is copied to the container. 88 | ADD examples/app.py /nonocaptcha 89 | 90 | # Uncomment the lines below if you want to autostart the app and expose the 91 | # port on your machine, which can be accessed by going to http://localhost:5000 92 | # RUN python3.6 /nonocaptcha/app.py 93 | # EXPOSE 5000 -------------------------------------------------------------------------------- /nonocaptcha/proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Proxy manager module. """ 5 | 6 | import time 7 | 8 | from threading import RLock 9 | from peewee import SqliteDatabase, Model, CharField, BooleanField, IntegerField 10 | 11 | 12 | database_filename = "proxy.db" 13 | database = db = SqliteDatabase( 14 | database_filename, pragmas=( 15 | { 16 | "synchronous": "off", 17 | "journal_mode": "wal", 18 | "cache_size": -1024 * 64 19 | } 20 | ) 21 | ) 22 | 23 | 24 | def init_db(dbname, remove=False): 25 | database.connect() 26 | database.create_tables([Proxy]) 27 | Proxy.update(active=False).execute() 28 | 29 | 30 | class Proxy(Model): 31 | class Meta: 32 | database = database 33 | 34 | proxy = CharField(primary_key=True) 35 | active = BooleanField(default=False) 36 | alive = BooleanField(default=False) 37 | last_used = IntegerField(default=0) 38 | last_banned = IntegerField(default=0) 39 | 40 | def __repr__(self): 41 | return ( 42 | f"Proxy(ip={self.proxy}, active={self.active},", 43 | f"alive={self.alive}, last_used={self.last_used},", 44 | f"last_banned={self.last_banned})", 45 | ) 46 | 47 | 48 | # import os; if os.path.exists(database_filename): os.remove(database_filename) 49 | init_db(database_filename) 50 | 51 | 52 | class ProxyDB(object): 53 | _lock = RLock() 54 | 55 | def __init__(self, last_banned_timeout=300): 56 | self.last_banned_timeout = last_banned_timeout 57 | 58 | def add(self, proxies): 59 | def chunks(l, n): 60 | n = max(1, n) 61 | return (l[i:i + n] for i in range(0, len(l), n)) 62 | 63 | q = [proxy.proxy for proxy in Proxy.select(Proxy.proxy)] 64 | proxies_up = list(set(q) & set(proxies)) 65 | proxies_dead = list(set(q) - set(proxies)) 66 | proxies_new = set(proxies) - set(q) 67 | rows = [{"proxy": proxy, "alive": True} for proxy in proxies_new] 68 | 69 | with db.atomic(): 70 | for dead in chunks(proxies_dead, 500): 71 | Proxy.update(alive=False).where(Proxy.proxy << dead).execute() 72 | 73 | for up in chunks(proxies_up, 500): 74 | Proxy.update(alive=True).where(Proxy.proxy << up).execute() 75 | 76 | for row in chunks(rows, 100): 77 | Proxy.insert_many(row).execute() 78 | 79 | def get(self): 80 | try: 81 | proxy = ( 82 | Proxy.select(Proxy.proxy) 83 | .where( 84 | (Proxy.active == 0) 85 | & (Proxy.alive == 1) 86 | & ( 87 | ( 88 | Proxy.last_banned + self.last_banned_timeout 89 | <= time.time() 90 | ) 91 | | (Proxy.last_banned == 0) 92 | ) 93 | ) 94 | .order_by(Proxy.last_used) 95 | .get() 96 | .proxy 97 | ) 98 | self.set_active(proxy, is_active=True) 99 | except Proxy.DoesNotExist: 100 | return 101 | return proxy 102 | 103 | def set_active(self, proxy, is_active): 104 | """Returns None""" 105 | with self._lock: 106 | Proxy.update(active=is_active).where( 107 | Proxy.proxy == proxy 108 | ).execute() 109 | if is_active: 110 | Proxy.update(last_used=time.time()).where( 111 | Proxy.proxy == proxy 112 | ).execute() 113 | 114 | def set_banned(self, proxy): 115 | """Returns None""" 116 | with self._lock: 117 | Proxy.update(last_banned=time.time(), active=False).where( 118 | Proxy.proxy == proxy 119 | ).execute() 120 | -------------------------------------------------------------------------------- /nonocaptcha/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Utility functions. """ 5 | 6 | import os 7 | import sys 8 | import aiohttp 9 | import aiofiles 10 | import asyncio 11 | import pickle 12 | import requests 13 | import itertools 14 | 15 | from functools import partial, wraps 16 | 17 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 18 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 19 | 20 | __all__ = [ 21 | "save_file", 22 | "load_file", 23 | "get_page", 24 | "threaded", 25 | "serialize", 26 | "deserialize", 27 | ] 28 | 29 | 30 | def threaded(func): 31 | @wraps(func) 32 | async def wrap(*args, **kwargs): 33 | loop = asyncio.get_event_loop() 34 | 35 | return await loop.run_in_executor(None, partial(func, *args, **kwargs)) 36 | 37 | return wrap 38 | 39 | 40 | async def save_file(file, data, binary=False): 41 | mode = "w" if not binary else "wb" 42 | async with aiofiles.open(file, mode=mode) as f: 43 | await f.write(data) 44 | 45 | 46 | async def load_file(file, binary=False): 47 | mode = "r" if not binary else "rb" 48 | async with aiofiles.open(file, mode=mode) as f: 49 | return await f.read() 50 | 51 | 52 | @threaded 53 | def get_page_win( 54 | url, 55 | proxy=None, 56 | proxy_auth=None, 57 | binary=False, 58 | verify=False, 59 | timeout=300 60 | ): 61 | proxies = None 62 | if proxy: 63 | if proxy_auth: 64 | proxy = proxy.replace("http://", "") 65 | username = proxy_auth['username'] 66 | password = proxy_auth['password'] 67 | proxies = { 68 | "http": f"http://{username}:{password}@{proxy}", 69 | "https": f"http://{username}:{password}@{proxy}", 70 | } 71 | else: 72 | proxies = {"http": proxy, "https": proxy} 73 | with requests.Session() as session: 74 | response = session.get( 75 | url, 76 | proxies=proxies, 77 | verify=verify, 78 | timeout=timeout 79 | ) 80 | if binary: 81 | return response.content 82 | return response.text 83 | 84 | 85 | async def get_page( 86 | url, 87 | proxy=None, 88 | proxy_auth=None, 89 | binary=False, 90 | verify=False, 91 | timeout=300 92 | ): 93 | if sys.platform == "win32": 94 | # SSL Doesn't work on aiohttp through ProactorLoop so we use Requests 95 | return await get_page_win( 96 | url, proxy, proxy_auth, binary, verify, timeout 97 | ) 98 | else: 99 | if proxy_auth: 100 | proxy_auth = aiohttp.BasicAuth( 101 | proxy_auth['username'], proxy_auth['password'] 102 | ) 103 | async with aiohttp.ClientSession() as session: 104 | async with session.get( 105 | url, 106 | proxy=proxy, 107 | proxy_auth=proxy_auth, 108 | verify_ssl=verify, 109 | timeout=timeout 110 | ) as response: 111 | if binary: 112 | return await response.read() 113 | return await response.text() 114 | 115 | 116 | def serialize(obj, p): 117 | """Must be synchronous to prevent corrupting data""" 118 | with open(p, "wb") as f: 119 | pickle.dump(obj, f) 120 | 121 | 122 | async def deserialize(p): 123 | data = await load_file(p, binary=True) 124 | return pickle.loads(data) 125 | 126 | 127 | def split_image(image_obj, pieces, save_to): 128 | """Splits an image into constituent pictures of x""" 129 | width, height = image_obj.size 130 | if pieces == 9: 131 | # Only case solved so far 132 | row_length = 3 133 | interval = width // row_length 134 | for x, y in itertools.product(range(row_length), repeat=2): 135 | cropped = image_obj.crop((interval*x, interval*y, 136 | interval*(x+1), interval*(y+1))) 137 | cropped.save(os.path.join(save_to, f'{y*row_length+x}.jpg')) 138 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/mikeyy/nonoCAPTCHA.svg?branch=master 2 | :target: https://travis-ci.org/mikeyy/nonoCAPTCHA 3 | .. image:: https://img.shields.io/pypi/v/nonocaptcha.svg 4 | :alt: PyPI 5 | :target: https://pypi.org/project/nonocaptcha/ 6 | .. image:: https://img.shields.io/pypi/pyversions/nonocaptcha.svg 7 | :alt: PyPI - Python Version 8 | :target: https://pypi.org/project/nonocaptcha/ 9 | .. image:: https://img.shields.io/pypi/l/nonocaptcha.svg 10 | :alt: PyPI - License 11 | :target: https://pypi.org/project/nonocaptcha/ 12 | .. image:: https://img.shields.io/pypi/status/nonocaptcha.svg 13 | :alt: PyPI - Status 14 | :target: https://pypi.org/project/nonocaptcha/ 15 | 16 | nonoCAPTCHA 17 | =========== 18 | 19 | An async Python library to automate solving ReCAPTCHA v2 by audio using 20 | Mozilla's DeepSpeech, PocketSphinx, Microsoft Azure’s, and Amazon's Transcribe 21 | Speech-to-Text API. Built with Pyppeteer for Chrome automation framework 22 | and similarities to Puppeteer, PyDub for easily converting MP3 files into WAV, 23 | aiohttp for async minimalistic web-server, and Python’s built-in AsyncIO 24 | for convenience. 25 | 26 | Disclaimer 27 | ---------- 28 | 29 | This project is for educational and research purposes only. Any actions 30 | and/or activities related to the material contained on this GitHub 31 | Repository is solely your responsibility. The misuse of the information 32 | in this GitHub Repository can result in criminal charges brought against 33 | the persons in question. The author will not be held responsible in the 34 | event any criminal charges be brought against any individuals misusing 35 | the information in this GitHub Repository to break the law. 36 | 37 | Public 38 | ------ 39 | 40 | This script was first featured on Reddit at 41 | `/r/Python `__ - `see 42 | here `__ 43 | for the thread. I’ve finally decided to release the script. 44 | 45 | Preview 46 | ------- 47 | 48 | Check out 1-minute presentation of the script in action, with only 49 | 8 threads! 50 | 51 | .. figure:: https://github.com/mikeyy/nonoCAPTCHA/blob/presentation/presentation.gif 52 | :alt: nonoCAPTCHA preview 53 | 54 | Compatibility 55 | ------------- 56 | 57 | Linux, macOS, and Windows! 58 | 59 | Requirements 60 | ------------ 61 | 62 | Python 63 | `3.6.0 `__ - 64 | `3.7.0 `__, 65 | `FFmpeg `__, a `Microsoft 66 | Azure `__ account for Bing Speech API access, an 67 | Amazon Web Services account for Transcribe and S3 access, and for Pocketsphinx 68 | you'll need pulseaudio, swig, libasound2-dev, and libpulse-dev under Ubuntu. 69 | 70 | Installation 71 | ------------ 72 | 73 | .. code:: shell 74 | 75 | $ pip install nonocaptcha 76 | 77 | Configuration 78 | ------------- 79 | 80 | Please edit nonocaptcha.example.yaml and save as nonocaptcha.yaml 81 | 82 | Usage 83 | ----- 84 | 85 | If you want to use it in your own script 86 | 87 | .. code:: python 88 | 89 | import asyncio 90 | from nonocaptcha.solver import Solver 91 | 92 | pageurl = "https://www.google.com/recaptcha/api2/demo" 93 | sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" 94 | 95 | proxy = "127.0.0.1:1000" 96 | auth_details = { 97 | "username": "user", 98 | "password": "pass" 99 | } 100 | args = ["--timeout 5"] 101 | options = {"ignoreHTTPSErrors": True, "args": args} 102 | client = Solver( 103 | pageurl, 104 | sitekey, 105 | options=options, 106 | proxy=proxy, 107 | proxy_auth=auth_details, 108 | ) 109 | 110 | solution = asyncio.get_event_loop().run_until_complete(client.start()) 111 | if solution: 112 | print(solution) 113 | 114 | Donations 115 | --------- 116 | 117 | The use of proxies are required for my continuous updates and fixes on 118 | nonoCAPTCHA. Any donations would be a great help in allowing me to purchase 119 | these proxies, that are clearly expensive. If anyone is willing to share 120 | their proxies, I wouldn't hesitate to accept the offer. 121 | 122 | Bitcoin: 1BfWQWAZBsSKCNQZgsq2vwaKxYvkrhb14u -------------------------------------------------------------------------------- /nonocaptcha/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Base module. """ 5 | 6 | import asyncio 7 | import logging 8 | import os 9 | import random 10 | 11 | from nonocaptcha import package_dir 12 | from nonocaptcha.exceptions import SafePassage, TryAgain 13 | 14 | FORMAT = "%(asctime)s %(message)s" 15 | logging.basicConfig(format=FORMAT) 16 | 17 | try: 18 | import yaml 19 | with open("nonocaptcha.yaml") as f: 20 | settings = yaml.load(f) 21 | except FileNotFoundError: 22 | print( 23 | "Solver can't run without a configuration file!\n" 24 | "An example (nonocaptcha.example.yaml) has been copied to your folder." 25 | ) 26 | 27 | import sys 28 | from shutil import copyfile 29 | 30 | copyfile( 31 | f"{package_dir}/nonocaptcha.example.yaml", "nonocaptcha.example.yaml" 32 | ) 33 | sys.exit(0) 34 | 35 | 36 | class Clicker: 37 | @staticmethod 38 | async def click_button(button): 39 | click_delay = random.uniform(30, 170) 40 | await button.click(delay=click_delay) 41 | 42 | 43 | class Base(Clicker): 44 | logger = logging.getLogger(__name__) 45 | if settings["debug"]: 46 | logger.setLevel("DEBUG") 47 | proc_id = 0 48 | headless = settings["headless"] 49 | keyboard_traverse = settings["keyboard_traverse"] 50 | should_block_images = settings["block_images"] 51 | page_load_timeout = settings["timeout"]["page_load"] * 1000 52 | deface_timeout = settings["timeout"]["deface"] * 1000 53 | animation_timeout = settings["timeout"]["animation"] * 1000 54 | speech_service = settings["speech"]["service"] 55 | deface_data = os.path.join(package_dir, settings["data"]["deface_html"]) 56 | jquery_data = os.path.join(package_dir, settings["data"]["jquery_js"]) 57 | override_data = os.path.join(package_dir, settings["data"]["override_js"]) 58 | 59 | async def get_frames(self): 60 | self.checkbox_frame = next( 61 | frame for frame in self.page.frames if "api2/anchor" in frame.url 62 | ) 63 | self.image_frame = next( 64 | frame for frame in self.page.frames if "api2/bframe" in frame.url 65 | ) 66 | 67 | async def click_reload_button(self): 68 | reload_button = await self.image_frame.J("#recaptcha-reload-button") 69 | await self.click_button(reload_button) 70 | 71 | async def check_detection(self, timeout): 72 | """Checks if "Try again later", "please solve more" modal appears 73 | or success""" 74 | 75 | func = """(function() { 76 | checkbox_frame = parent.window.$("iframe[src*='api2/anchor']").contents(); 77 | image_frame = parent.window.$("iframe[src*='api2/bframe']").contents(); 78 | 79 | var bot_header = $(".rc-doscaptcha-header-text", image_frame) 80 | if(bot_header.length){ 81 | if(bot_header.text().indexOf("Try again later") > -1){ 82 | parent.window.wasdetected = true; 83 | return true; 84 | } 85 | } 86 | 87 | var try_again_header = $(".rc-audiochallenge-error-message", image_frame) 88 | if(try_again_header.length){ 89 | if(try_again_header.text().indexOf("please solve more") > -1){ 90 | try_again_header.text('Trying again...') 91 | parent.window.tryagain = true; 92 | return true; 93 | } 94 | } 95 | 96 | var checkbox_anchor = $("#recaptcha-anchor", checkbox_frame); 97 | if(checkbox_anchor.attr("aria-checked") === "true"){ 98 | parent.window.success = true; 99 | return true; 100 | } 101 | 102 | })()""" 103 | try: 104 | await self.page.waitForFunction(func, timeout=timeout) 105 | except asyncio.TimeoutError: 106 | raise SafePassage() 107 | else: 108 | if await self.page.evaluate("parent.window.wasdetected === true;"): 109 | status = "detected" 110 | elif await self.page.evaluate("parent.window.success === true"): 111 | status = "success" 112 | elif await self.page.evaluate("parent.window.tryagain === true"): 113 | await self.page.evaluate("parent.window.tryagain = false;") 114 | raise TryAgain() 115 | 116 | return {"status": status} 117 | 118 | def log(self, message): 119 | self.logger.debug(f"{self.proc_id} {message}") 120 | -------------------------------------------------------------------------------- /nonocaptcha/launcher.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Launcher module. Workarounds to launch browsers asynchronously. """ 5 | 6 | import asyncio 7 | import json 8 | import logging 9 | import os 10 | 11 | from urllib.request import urlopen 12 | from urllib.error import URLError 13 | 14 | from pyppeteer import launcher 15 | from pyppeteer.browser import Browser 16 | from pyppeteer.connection import Connection 17 | from pyppeteer.errors import BrowserError 18 | from pyppeteer.util import check_chromium, chromium_excutable 19 | from pyppeteer.util import download_chromium, merge_dict, get_free_port 20 | 21 | 22 | class Launcher(launcher.Launcher): 23 | """Chrome parocess launcher class.""" 24 | 25 | def __init__(self, options, # noqa: C901 26 | **kwargs) -> None: 27 | """Make new launcher.""" 28 | self.options = merge_dict(options, kwargs) 29 | self.port = get_free_port() 30 | self.url = f'http://127.0.0.1:{self.port}' 31 | self.chrome_args = [f'--remote-debugging-port={self.port}'] 32 | self._loop = self.options.get('loop', asyncio.get_event_loop()) 33 | 34 | logLevel = self.options.get('logLevel') 35 | if logLevel: 36 | logging.getLogger('pyppeteer').setLevel(logLevel) 37 | self.chromeClosed = True 38 | if self.options.get('appMode', False): 39 | self.options['headless'] = False 40 | self._tmp_user_data_dir = None 41 | self._parse_args() 42 | if self.options.get('devtools'): 43 | self.chrome_args.append('--auto-open-devtools-for-tabs') 44 | self.options['headless'] = False 45 | if 'headless' not in self.options or self.options.get('headless'): 46 | self.chrome_args.extend([ 47 | '--headless', 48 | '--disable-gpu', 49 | '--hide-scrollbars', 50 | '--mute-audio', 51 | ]) 52 | if 'executablePath' in self.options: 53 | self.exec = self.options['executablePath'] 54 | else: 55 | if not check_chromium(): 56 | download_chromium() 57 | self.exec = str(chromium_excutable()) 58 | self.cmd = [self.exec] + self.chrome_args 59 | 60 | async def launch(self): 61 | self.chromeClosed = False 62 | self.connection = None 63 | env = self.options.get("env") 64 | self.proc = await asyncio.subprocess.create_subprocess_exec( 65 | *self.cmd, 66 | stdout=asyncio.subprocess.DEVNULL, 67 | stderr=asyncio.subprocess.DEVNULL, 68 | env=env, 69 | ) 70 | # Signal handlers for exits used to be here 71 | connectionDelay = self.options.get("slowMo", 0) 72 | self.browserWSEndpoint = await self._get_ws_endpoint() 73 | self.connection = Connection( 74 | self.browserWSEndpoint, self._loop, connectionDelay) 75 | return await Browser.create( 76 | self.connection, self.options, self.proc, self.killChrome) 77 | 78 | async def _get_ws_endpoint(self) -> str: 79 | url = self.url + '/json/version' 80 | while self.proc.returncode is None: 81 | await asyncio.sleep(0.1) 82 | try: 83 | with urlopen(url) as f: 84 | data = json.loads(f.read().decode()) 85 | break 86 | except URLError as e: 87 | continue 88 | else: 89 | raise BrowserError( 90 | 'Browser closed unexpectedly:\n{}'.format( 91 | await self.proc.stdout.read().decode() 92 | ) 93 | ) 94 | return data['webSocketDebuggerUrl'] 95 | 96 | async def waitForChromeToClose(self): 97 | if self.proc.returncode is None and not self.chromeClosed: 98 | self.chromeClosed = True 99 | try: 100 | self.proc.terminate() 101 | await self.proc.wait() 102 | except OSError: 103 | pass 104 | 105 | async def killChrome(self): 106 | """Terminate chromium process.""" 107 | if self.connection and self.connection._connected: 108 | try: 109 | await self.connection.send("Browser.close") 110 | await self.connection.dispose() 111 | except Exception: 112 | pass 113 | if self._tmp_user_data_dir and os.path.exists(self._tmp_user_data_dir): 114 | # Force kill chrome only when using temporary userDataDir 115 | await self.waitForChromeToClose() 116 | self._cleanup_tmp_user_data_dir() 117 | -------------------------------------------------------------------------------- /examples/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Example run functions.""" 5 | 6 | import asyncio 7 | import random 8 | import sys 9 | 10 | from async_timeout import timeout 11 | 12 | from nonocaptcha import util 13 | from nonocaptcha.proxy import ProxyDB 14 | from nonocaptcha.solver import Solver 15 | 16 | threads = 1 # Max browsers to open 17 | sort_position = False 18 | 19 | pageurl = "https://www.google.com/recaptcha/api2/demo" 20 | sitekey = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-" 21 | 22 | proxy_source = None # Can be URL or file location 23 | proxy_username, proxy_password = (None, None) 24 | 25 | 26 | def shuffle(i): 27 | random.shuffle(i) 28 | return i 29 | 30 | 31 | if sort_position: 32 | screen_width, screen_height = (1400, 1050) 33 | threads = int(1 + screen_width / 400 + 1 + screen_height / 400) 34 | 35 | position_x = 20 36 | position_y = 20 37 | 38 | positions = [] 39 | used_positions = [] 40 | 41 | positions.append((position_x, position_y)) 42 | for i in range(threads): 43 | position_x += 400 44 | if position_x > screen_width: 45 | position_y += 400 46 | if position_y > screen_height: 47 | position_y = 20 48 | position_x = 20 49 | positions.append((position_x, position_y)) 50 | 51 | 52 | class Run(object): 53 | proxies_loading = True 54 | 55 | def __init__(self, loop): 56 | self.proxies = ProxyDB(last_banned_timeout=45*60) 57 | if proxy_source: 58 | asyncio.ensure_future(self.get_proxies(), loop=loop) 59 | 60 | async def get_proxies(self): 61 | while True: 62 | self.proxies_loading = True 63 | print("Proxies loading...") 64 | protos = ["http://", "https://"] 65 | if any(p in proxy_source for p in protos): 66 | f = util.get_page 67 | else: 68 | f = util.load_file 69 | 70 | result = await f(proxy_source) 71 | self.proxies.add(result.split('\n')) 72 | self.proxies_loading = False 73 | print("Proxies loaded.") 74 | await asyncio.sleep(10 * 60) 75 | 76 | async def work(self): 77 | args = ["--timeout 5"] 78 | if sort_position: 79 | this_position = next( 80 | x for x in positions if x not in used_positions 81 | ) 82 | used_positions.append(this_position) 83 | args.extend( 84 | [ 85 | "--window-position=%s,%s" % this_position, 86 | "--window-size=400,400", 87 | ] 88 | ) 89 | options = { 90 | "ignoreHTTPSErrors": True, 91 | "args": args 92 | } 93 | proxy = self.proxies.get() if proxy_source else None 94 | proxy_auth = None 95 | if proxy_username and proxy_password: 96 | proxy_auth = {"username": proxy_username, 97 | "password": proxy_password} 98 | client = Solver( 99 | pageurl, 100 | sitekey, 101 | options=options, 102 | proxy=proxy, 103 | proxy_auth=proxy_auth 104 | ) 105 | result = None 106 | try: 107 | async with timeout(180): 108 | result = await client.start() 109 | finally: 110 | if sort_position: 111 | used_positions.remove(this_position) 112 | 113 | if result: 114 | self.proxies.set_active(proxy, False) 115 | if result['status'] == "detected": 116 | self.proxies.set_banned(proxy) 117 | else: 118 | if result['status'] == "success": 119 | return result['code'] 120 | 121 | async def main(self): 122 | if proxy_source: 123 | while not self.proxies_loading: 124 | await asyncio.sleep(1) 125 | 126 | tasks = [asyncio.ensure_future(self.work()) for i in range(threads)] 127 | completed, pending = await asyncio.wait( 128 | tasks, return_when=asyncio.FIRST_COMPLETED 129 | ) 130 | count = 0 131 | while True: 132 | for task in completed: 133 | result = task.result() 134 | if result: 135 | count += 1 136 | print(f"{count}: {result}") 137 | pending.add(asyncio.ensure_future(self.work())) 138 | completed, pending = await asyncio.wait( 139 | pending, return_when=asyncio.FIRST_COMPLETED 140 | ) 141 | 142 | 143 | if sys.platform == "win32": 144 | loop = asyncio.ProactorEventLoop() 145 | asyncio.set_event_loop(loop) 146 | else: 147 | loop = asyncio.get_event_loop() 148 | 149 | r = Run(loop) 150 | loop.run_until_complete(r.main()) 151 | -------------------------------------------------------------------------------- /nonocaptcha/audio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Audio solving module. """ 5 | 6 | import os 7 | import random 8 | import shutil 9 | import tempfile 10 | 11 | from asyncio import TimeoutError, CancelledError 12 | from aiohttp.client_exceptions import ClientError 13 | 14 | from nonocaptcha import util 15 | from nonocaptcha.speech import Amazon, Azure, Sphinx, DeepSpeech 16 | from nonocaptcha.base import Base 17 | from nonocaptcha.exceptions import DownloadError, ReloadError, TryAgain 18 | 19 | 20 | class SolveAudio(Base): 21 | def __init__(self, page, loop, proxy, proxy_auth, proc_id): 22 | self.page = page 23 | self.loop = loop 24 | self.proxy = proxy 25 | self.proxy_auth = proxy_auth 26 | self.proc_id = proc_id 27 | 28 | async def solve_by_audio(self): 29 | """Go through procedures to solve audio""" 30 | await self.get_frames() 31 | for i in range(10): 32 | try: 33 | answer = await self.loop.create_task( 34 | self.get_audio_response()) 35 | except DownloadError: 36 | raise 37 | except ReloadError: 38 | raise 39 | else: 40 | if not answer: 41 | continue 42 | await self.type_audio_response(answer) 43 | await self.click_verify() 44 | try: 45 | result = await self.check_detection(self.animation_timeout) 46 | except TryAgain: 47 | continue 48 | else: 49 | return result 50 | else: 51 | return {"status": "retries_exceeded"} 52 | 53 | async def get_audio_response(self): 54 | """Download audio data then send to speech-to-text API for answer""" 55 | 56 | try: 57 | audio_url = await self.image_frame.evaluate( 58 | '$("#audio-source").attr("src")') 59 | if not isinstance(audio_url, str): 60 | raise DownloadError("Audio url is not valid, aborting") 61 | except CancelledError: 62 | raise DownloadError("Audio url not found, aborting") 63 | 64 | self.log("Downloading audio file") 65 | try: 66 | audio_data = await self.loop.create_task( 67 | util.get_page( 68 | audio_url, 69 | proxy=self.proxy, 70 | proxy_auth=self.proxy_auth, 71 | binary=True, 72 | timeout=self.page_load_timeout)) 73 | except ClientError as e: 74 | self.log(f"Error `{e}` occured during audio download, retrying") 75 | else: 76 | answer = None 77 | service = self.speech_service.lower() 78 | if service in ["azure", "pocketsphinx", "deepspeech"]: 79 | if service == "azure": 80 | speech = Azure() 81 | elif service == "pocketsphinx": 82 | speech = Sphinx() 83 | else: 84 | speech = DeepSpeech() 85 | tmpd = tempfile.mkdtemp() 86 | tmpf = os.path.join(tmpd, "audio.mp3") 87 | await util.save_file(tmpf, data=audio_data, binary=True) 88 | answer = await self.loop.create_task(speech.get_text(tmpf)) 89 | shutil.rmtree(tmpd) 90 | else: 91 | speech = Amazon() 92 | answer = await self.loop.create_task( 93 | speech.get_text(audio_data)) 94 | if answer: 95 | self.log(f'Received answer "{answer}"') 96 | return answer 97 | 98 | self.log("No answer, reloading") 99 | await self.click_reload_button() 100 | func = ( 101 | f'"{audio_url}" !== ' 102 | f'$(".rc-audiochallenge-tdownload-link").attr("href")') 103 | try: 104 | await self.image_frame.waitForFunction( 105 | func, timeout=self.animation_timeout) 106 | except TimeoutError: 107 | raise ReloadError("Download link never updated") 108 | 109 | async def type_audio_response(self, answer): 110 | self.log("Typing audio response") 111 | response_input = await self.image_frame.J("#audio-response") 112 | length = random.uniform(70, 130) 113 | await response_input.type(text=answer, delay=length) 114 | 115 | async def click_verify(self): 116 | if self.keyboard_traverse: 117 | response_input = await self.image_frame.J("#audio-response") 118 | self.log("Pressing Enter") 119 | await response_input.press("Enter") 120 | else: 121 | verify_button = await self.image_frame.J( 122 | "#recaptcha-verify-button") 123 | self.log("Clicking verify") 124 | await self.click_button(verify_button) 125 | -------------------------------------------------------------------------------- /nonocaptcha/image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ ***IN TESTING*** """ 5 | 6 | import os 7 | import asyncio 8 | import threading 9 | from PIL import Image 10 | from http.server import HTTPServer, BaseHTTPRequestHandler 11 | 12 | from nonocaptcha import util 13 | from nonocaptcha.base import Base, settings 14 | from nonocaptcha import package_dir 15 | 16 | PICTURES = os.path.join(package_dir, settings['data']['pictures']) 17 | 18 | 19 | class Handler(BaseHTTPRequestHandler): 20 | base_path = None 21 | 22 | def do_GET(self): 23 | self.send_response(200) 24 | self.end_headers() 25 | image_file = os.path.join(self.base_path, self.path.lstrip('/')) 26 | self.wfile.write(open(image_file, 'rb').read()) 27 | 28 | 29 | class SolveImage(Base): 30 | url = 'https://www.google.com/searchbyimage?site=search&sa=X&image_url=' 31 | ip_address = 'http://91.121.226.109' 32 | 33 | def __init__(self, browser, image_frame, proxy, proxy_auth, proc_id): 34 | self.browser = browser 35 | self.image_frame = image_frame 36 | self.proxy = proxy 37 | self.proxy_auth = proxy_auth 38 | self.proc_id = proc_id 39 | self.cur_image_path = None 40 | self.title = None 41 | self.pieces = None 42 | 43 | async def get_images(self): 44 | table = await self.image_frame.querySelector('table') 45 | rows = await table.querySelectorAll('tr') 46 | for row in rows: 47 | cells = await row.querySelectorAll('td') 48 | for cell in cells: 49 | yield cell 50 | 51 | async def is_solvable(self): 52 | el = await self.get_description_element() 53 | desc = await self.image_frame.evaluate('el => el.innerText', el) 54 | return 'images' in desc 55 | 56 | async def pictures_of(self): 57 | el = await self.get_description_element() 58 | of = await self.image_frame.evaluate( 59 | 'el => el.firstElementChild.innerText', el 60 | ) 61 | return of.lstrip('a ') 62 | 63 | async def get_description_element(self): 64 | name1 = await self.image_frame.querySelector('.rc-imageselect-desc') 65 | name2 = await self.image_frame.querySelector( 66 | '.rc-imageselect-desc-no-canonical' 67 | ) 68 | return name1 if name1 else name2 69 | 70 | async def cycle_to_solvable(self): 71 | while not await self.is_solvable() or await self.image_no() != 9: 72 | await self.click_reload_button() 73 | 74 | async def solve_by_image(self): 75 | await self.cycle_to_solvable() 76 | title = await self.pictures_of() 77 | pieces = 9 # TODO: crop other sizes 78 | image = await self.download_image() 79 | self.title = title 80 | print(f'Image of {title}') 81 | self.pieces = pieces 82 | self.cur_image_path = os.path.join(PICTURES, f'{hash(image)}') 83 | os.mkdir(self.cur_image_path) 84 | file_path = os.path.join(self.cur_image_path, f'{title}.jpg') 85 | await util.save_file(file_path, image, binary=True) 86 | image_obj = Image.open(file_path) 87 | util.split_image(image_obj, pieces, self.cur_image_path) 88 | self.start_app() 89 | queries = [self.reverse_image_search(i) for i in range(pieces)] 90 | results = await asyncio.gather(*queries, return_exceptions=True) 91 | for r in results: 92 | if isinstance(r, tuple) and r[1] is True: 93 | pass 94 | # TODO: return a list of numbers corresponding to image index 95 | 96 | return {'status': '?'} 97 | 98 | async def get_image_url(self): 99 | image_url = ( 100 | 'document.getElementsByClassName("rc-image-tile-wrapper")[0].' 101 | 'getElementsByTagName("img")[0].src' 102 | ) 103 | return await self.image_frame.evaluate(image_url) 104 | 105 | async def image_no(self): 106 | return len([i async for i in self.get_images()]) 107 | 108 | async def download_image(self): 109 | image_url = await self.get_image_url() 110 | return await util.get_page( 111 | image_url, self.proxy, self.proxy_auth, binary=True 112 | ) 113 | 114 | async def reverse_image_search(self, image_no): 115 | image_path = f'{self.ip_address}:8080/{image_no}.jpg' 116 | url = self.url + image_path 117 | page = await self.browser.newPage() 118 | await page.goto(url) 119 | card = await page.querySelector('div.card-section') 120 | if card: 121 | best_guess = await page.evaluate('el => el.children[1].innerText', 122 | card) 123 | print(image_no, best_guess) 124 | else: 125 | best_guess = '' 126 | await asyncio.sleep(100) 127 | await page.close() 128 | return self.title in best_guess 129 | 130 | def start_app(self): 131 | Handler.base_path = self.cur_image_path 132 | httpd = HTTPServer(('0.0.0.0', 8080), Handler) 133 | threading.Thread(target=httpd.serve_forever).start() 134 | -------------------------------------------------------------------------------- /examples/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Threaded example using executor to create a new event loop for running a 5 | task. Each task will continue to retry solving until it succeeds or times-out 6 | per the specified duration. Default is 180 seconds (3 minutes). On shutdown 7 | cleanup will propagate, hopefully closing left-over browsers and removing 8 | temporary profile folders. 9 | """ 10 | 11 | import asyncio 12 | import shutil 13 | import sys 14 | 15 | from aiohttp import web 16 | from async_timeout import timeout 17 | from concurrent.futures import ThreadPoolExecutor 18 | from functools import partial 19 | from pathlib import Path 20 | from threading import RLock 21 | 22 | from nonocaptcha import util 23 | from nonocaptcha.proxy import ProxyDB 24 | from nonocaptcha.solver import Solver 25 | 26 | SECRET_KEY = "CHANGEME" 27 | BANNED_TIMEOUT = 45*60 # 45 minutes 28 | SOLVE_DURATION = 3*60 # 3 minutes 29 | 30 | proxies = ProxyDB(last_banned_timeout=BANNED_TIMEOUT) 31 | proxy_source = None # Can be URL or file location 32 | proxy_username, proxy_password = (None, None) 33 | 34 | if sys.platform == "win32": 35 | parent_loop = asyncio.ProactorEventLoop() 36 | asyncio.set_event_loop(parent_loop) 37 | else: 38 | parent_loop = asyncio.get_event_loop() 39 | 40 | asyncio.set_child_watcher(asyncio.SafeChildWatcher()) 41 | asyncio.get_child_watcher().attach_loop(parent_loop) 42 | 43 | app = web.Application() 44 | 45 | # Clear Chrome temporary profiles 46 | dir = f"{Path.home()}/.pyppeteer/.dev_profile" 47 | shutil.rmtree(dir, ignore_errors=True) 48 | 49 | 50 | # Bugs are to be expected, despite my efforts. Apparently, event loops paired 51 | # with threads is nothing short of a hassle. 52 | class TaskRerun(object): 53 | 54 | def __init__(self, coro, duration): 55 | self.coro = coro 56 | self.duration = duration 57 | self._executor = ThreadPoolExecutor() 58 | self._lock = RLock() 59 | 60 | async def __aenter__(self): 61 | asyncio.ensure_future( 62 | asyncio.wrap_future( 63 | self._executor.submit(self.prepare_loop))) 64 | return self 65 | 66 | async def __aexit__(self, exc, exc_type, tb): 67 | asyncio.ensure_future( 68 | asyncio.wrap_future( 69 | asyncio.run_coroutine_threadsafe(self.cleanup(), self._loop))) 70 | return self 71 | 72 | def prepare_loop(self): 73 | # Surrounding the context around run_forever never releases the lock! 74 | with self._lock: 75 | self._loop = asyncio.new_event_loop() 76 | asyncio.set_event_loop(self._loop) 77 | self._loop.run_forever() 78 | 79 | async def start(self): 80 | with self._lock: 81 | # Blocking occurs unless wrapped in an asyncio.Future object 82 | task = asyncio.wrap_future( 83 | asyncio.run_coroutine_threadsafe( 84 | self.seek(), self._loop)) 85 | try: 86 | # Wait for the Task to complete or Timeout 87 | async with timeout(self.duration): 88 | await task 89 | result = task.result() 90 | except Exception: 91 | result = None 92 | finally: 93 | return result 94 | 95 | async def seek(self): 96 | def callback(task): 97 | # Consume Exception to satisfy event loop 98 | try: 99 | task.result() 100 | except Exception: 101 | pass 102 | while True: 103 | try: 104 | # Deadlock occurs unless wrapped in an asyncio.Future object. 105 | # We could also use AbstractEventLoop.create_task here. 106 | task = asyncio.wrap_future( 107 | asyncio.run_coroutine_threadsafe( 108 | self.coro(self._loop), self._loop)) 109 | task.add_done_callback(callback) 110 | await task 111 | result = task.result() 112 | if result is not None: 113 | return result 114 | except asyncio.CancelledError: 115 | break 116 | # We don't want to leave the loop unless result or cancelled 117 | except Exception: 118 | pass 119 | 120 | async def cleanup(self): 121 | # A maximum recursion depth occurs when current task gets called for 122 | # cancellation from gather. 123 | pending = tuple( 124 | task for task in asyncio.Task.all_tasks(loop=self._loop) 125 | if task is not asyncio.Task.current_task()) 126 | gathered = asyncio.gather( 127 | *pending, loop=self._loop, return_exceptions=True) 128 | gathered.cancel() 129 | await gathered 130 | self._loop.call_soon_threadsafe(self._loop.stop) 131 | self.executor.shutdown() 132 | 133 | 134 | async def work(pageurl, sitekey, loop): 135 | proxy = proxies.get() 136 | proxy_auth = None 137 | if proxy_username and proxy_password: 138 | proxy_auth = {"username": proxy_username, 139 | "password": proxy_password} 140 | options = {"ignoreHTTPSErrors": True, "args": ["--timeout 5"]} 141 | client = Solver( 142 | pageurl, 143 | sitekey, 144 | loop=loop, 145 | options=options, 146 | proxy=proxy, 147 | proxy_auth=proxy_auth 148 | ) 149 | result = await client.start() 150 | if result: 151 | if result['status'] == "detected": 152 | loop.call_soon_threadsafe(proxies.set_banned, proxy) 153 | else: 154 | if result['status'] == "success": 155 | return result['code'] 156 | 157 | 158 | async def get_solution(request): 159 | params = request.rel_url.query 160 | pageurl = params.get("pageurl") 161 | sitekey = params.get("sitekey") 162 | secret_key = params.get("secret_key") 163 | if not pageurl or not sitekey or not secret_key: 164 | response = {"error": "invalid request"} 165 | else: 166 | if secret_key != SECRET_KEY: 167 | response = {"error": "unauthorized attempt logged"} 168 | else: 169 | if pageurl and sitekey: 170 | coro = partial(work, pageurl, sitekey) 171 | async with TaskRerun(coro, duration=SOLVE_DURATION) as t: 172 | result = await t.start() 173 | if result: 174 | response = {"solution": result} 175 | else: 176 | response = {"error": "worker timed-out"} 177 | return web.json_response(response) 178 | 179 | 180 | async def load_proxies(): 181 | print('Loading proxies') 182 | while 1: 183 | protos = ["http://", "https://"] 184 | if proxy_source is None: 185 | return 186 | if any(p in proxy_source for p in protos): 187 | f = util.get_page 188 | else: 189 | f = util.load_file 190 | 191 | try: 192 | result = await f(proxy_source) 193 | except Exception: 194 | continue 195 | else: 196 | proxies.add(result.split('\n')) 197 | print('Proxies loaded') 198 | await asyncio.sleep(10 * 60) 199 | 200 | 201 | async def start_background_tasks(app): 202 | app["dispatch"] = app.loop.create_task(load_proxies()) 203 | 204 | 205 | async def cleanup_background_tasks(app): 206 | app["dispatch"].cancel() 207 | await app["dispatch"] 208 | 209 | 210 | app.router.add_get("/", get_solution) 211 | app.on_startup.append(start_background_tasks) 212 | app.on_cleanup.append(cleanup_background_tasks) 213 | 214 | if __name__ == "__main__": 215 | web.run_app(app, host="0.0.0.0", port=5000) 216 | -------------------------------------------------------------------------------- /nonocaptcha/speech.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Speech module. Text-to-speech classes - Sphinx, Amazon, and Azure. """ 5 | 6 | import aiobotocore 7 | import aiofiles 8 | import asyncio 9 | import json 10 | import os 11 | import re 12 | import struct 13 | import sys 14 | import time 15 | import websockets 16 | 17 | from datetime import datetime 18 | from uuid import uuid4 19 | from pydub import AudioSegment 20 | from pocketsphinx.pocketsphinx import Decoder 21 | 22 | from nonocaptcha.base import settings 23 | from nonocaptcha import util 24 | 25 | 26 | @util.threaded 27 | def mp3_to_wav(mp3_filename): 28 | wav_filename = mp3_filename.replace(".mp3", ".wav") 29 | segment = AudioSegment.from_mp3(mp3_filename) 30 | sound = segment.set_channels(1).set_frame_rate(16000) 31 | garbage = len(sound) / 3.1 32 | sound = sound[+garbage:len(sound) - garbage] 33 | sound.export(wav_filename, format="wav") 34 | return wav_filename 35 | 36 | 37 | class DeepSpeech(object): 38 | MODEL_DIR = settings["speech"]["deepspeech"]["model_dir"] 39 | 40 | async def get_text(self, mp3_filename): 41 | wav_filename = await mp3_to_wav(mp3_filename) 42 | proc = await asyncio.create_subprocess_exec( 43 | *[ 44 | "deepspeech", 45 | os.path.join(self.MODEL_DIR, "output_graph.pb"), 46 | wav_filename, 47 | os.path.join(self.MODEL_DIR, "alphabet.txt"), 48 | os.path.join(self.MODEL_DIR, "lm.binary"), 49 | os.path.join(self.MODEL_DIR, "trie"), 50 | ], 51 | stdout=asyncio.subprocess.PIPE, 52 | ) 53 | if not proc.returncode: 54 | data = await proc.stdout.readline() 55 | result = data.decode("ascii").rstrip() 56 | await proc.wait() 57 | if result: 58 | return result 59 | 60 | 61 | class Sphinx(object): 62 | MODEL_DIR = settings["speech"]["pocketsphinx"]["model_dir"] 63 | 64 | @util.threaded 65 | def build_decoder(self): 66 | config = Decoder.default_config() 67 | config.set_string( 68 | "-dict", os.path.join(self.MODEL_DIR, "cmudict-en-us.dict") 69 | ) 70 | config.set_string( 71 | "-fdict", os.path.join(self.MODEL_DIR, "en-us/noisedict") 72 | ) 73 | config.set_string( 74 | "-featparams", os.path.join(self.MODEL_DIR, "en-us/feat.params") 75 | ) 76 | config.set_string( 77 | "-tmat", os.path.join(self.MODEL_DIR, "en-us/transition_matrices") 78 | ) 79 | config.set_string("-hmm", os.path.join(self.MODEL_DIR, "en-us")) 80 | config.set_string("-lm", os.path.join(self.MODEL_DIR, "en-us.lm.bin")) 81 | config.set_string("-mdef", os.path.join(self.MODEL_DIR, "en-us/mdef")) 82 | config.set_string("-mean", os.path.join(self.MODEL_DIR, "en-us/means")) 83 | config.set_string( 84 | "-sendump", os.path.join(self.MODEL_DIR, "en-us/sendump") 85 | ) 86 | config.set_string( 87 | "-var", os.path.join(self.MODEL_DIR, "en-us/variances") 88 | ) 89 | null_path = "/dev/null" 90 | if sys.platform == "win32": 91 | null_path = "NUL" 92 | config.set_string("-logfn", null_path) 93 | return Decoder(config) 94 | 95 | async def get_text(self, mp3_filename): 96 | decoder = await self.build_decoder() 97 | decoder.start_utt() 98 | wav_filename = await mp3_to_wav(mp3_filename) 99 | async with aiofiles.open(wav_filename, "rb") as stream: 100 | while True: 101 | buf = await stream.read(1024) 102 | if buf: 103 | decoder.process_raw(buf, False, False) 104 | else: 105 | break 106 | decoder.end_utt() 107 | hyp = " ".join([seg.word for seg in decoder.seg()]) 108 | answer = " ".join( 109 | re.sub("<[^<]+?>|\[[^<]+?\]|\([^<]+?\)", " ", hyp).split() 110 | ) 111 | return answer 112 | 113 | 114 | class Amazon(object): 115 | ACCESS_KEY_ID = settings["speech"]["amazon"]["secret_key_id"] 116 | SECRET_ACCESS_KEY = settings["speech"]["amazon"]["secret_access_key"] 117 | REGION_NAME = settings["speech"]["amazon"]["region"] 118 | S3_BUCKET = settings["speech"]["amazon"]["s3_bucket"] 119 | 120 | async def get_text(self, audio_data): 121 | session = aiobotocore.get_session() 122 | upload = session.create_client( 123 | "s3", 124 | region_name=self.REGION_NAME, 125 | aws_secret_access_key=self.SECRET_ACCESS_KEY, 126 | aws_access_key_id=self.ACCESS_KEY_ID, 127 | ) 128 | transcribe = session.create_client( 129 | "transcribe", 130 | region_name=self.REGION_NAME, 131 | aws_secret_access_key=self.SECRET_ACCESS_KEY, 132 | aws_access_key_id=self.ACCESS_KEY_ID, 133 | ) 134 | filename = f"{uuid4().hex}.mp3" 135 | # Upload audio file to bucket 136 | await upload.put_object( 137 | Bucket=self.S3_BUCKET, Key=filename, Body=audio_data 138 | ) 139 | job_name = uuid4().hex 140 | job_uri = ( 141 | f"https://s3.{self.REGION_NAME}.amazonaws.com/{self.S3_BUCKET}/" 142 | f"{filename}" 143 | ) 144 | # Send audio file URI to Transcribe 145 | await transcribe.start_transcription_job( 146 | TranscriptionJobName=job_name, 147 | Media={"MediaFileUri": job_uri}, 148 | MediaFormat="mp3", 149 | LanguageCode="en-US", 150 | ) 151 | # Wait 90 seconds for transcription 152 | timeout = 90 153 | while time.time() > timeout: 154 | status = await transcribe.get_transcription_job( 155 | TranscriptionJobName=job_name 156 | ) 157 | if status["TranscriptionJob"]["TranscriptionJobStatus"] in [ 158 | "COMPLETED", 159 | "FAILED", 160 | ]: 161 | break 162 | await asyncio.sleep(5) 163 | # Delete audio file from bucket 164 | await upload.delete_object(Bucket=self.S3_BUCKET, Key=filename) 165 | if "TranscriptFileUri" in status["TranscriptionJob"]["Transcript"]: 166 | transcript_uri = status["TranscriptionJob"]["Transcript"][ 167 | "TranscriptFileUri" 168 | ] 169 | data = json.loads(await util.get_page(transcript_uri)) 170 | transcript = data["results"]["transcripts"][0]["transcript"] 171 | return transcript 172 | 173 | # Delete audio file 174 | await upload.delete_object(Bucket=self.S3_BUCKET, Key=filename) 175 | 176 | # Close clients 177 | await upload._endpoint._aio_session.close() 178 | await transcribe._endpoint._aio_session.close() 179 | 180 | 181 | class Azure(object): 182 | SUB_KEY = settings["speech"]["azure"]["api_subkey"] 183 | 184 | @util.threaded 185 | def extract_json_body(self, response): 186 | pattern = "^\r\n" # header separator is an empty line 187 | m = re.search(pattern, response, re.M) 188 | return json.loads( 189 | response[m.end():] 190 | ) # assuming that content type is json 191 | 192 | @util.threaded 193 | def build_message(self, req_id, payload): 194 | message = b"" 195 | timestamp = datetime.utcnow().isoformat() 196 | header = ( 197 | f"X-RequestId: {req_id}\r\nX-Timestamp: {timestamp}Z\r\n" 198 | f"Path: audio\r\nContent-Type: audio/x-wav\r\n\r\n" 199 | ) 200 | message += struct.pack(">H", len(header)) 201 | message += header.encode() 202 | message += payload 203 | return message 204 | 205 | async def bytes_from_file(self, filename, chunksize=8192): 206 | async with aiofiles.open(filename, "rb") as f: 207 | while True: 208 | chunk = await f.read(chunksize) 209 | if chunk: 210 | yield chunk 211 | else: 212 | break 213 | 214 | async def send_file(self, websocket, filename): 215 | req_id = uuid4().hex 216 | async for payload in self.bytes_from_file(filename): 217 | message = await self.build_message(req_id, payload) 218 | await websocket.send(message) 219 | 220 | async def get_text(self, mp3_filename): 221 | wav_filename = await mp3_to_wav(mp3_filename) 222 | conn_id = uuid4().hex 223 | url = ( 224 | f"wss://speech.platform.bing.com/speech/recognition/dictation/cogn" 225 | f"itiveservices/v1?language=en-US&Ocp-Apim-Subscription-Key=" 226 | f"{self.SUB_KEY}&X-ConnectionId={conn_id}&format=detailed" 227 | ) 228 | async with websockets.connect(url) as websocket: 229 | await self.send_file(websocket, wav_filename) 230 | timeout = time.time() + 15 231 | while time.time() < timeout: 232 | response = await websocket.recv() 233 | content = await self.extract_json_body(response) 234 | if ( 235 | "RecognitionStatus" in content 236 | and content["RecognitionStatus"] == "Success" 237 | ): 238 | answer = content["NBest"][0]["Lexical"] 239 | return answer 240 | if ( 241 | "RecognitionStatus" in content 242 | and content["RecognitionStatus"] == "EndOfDictation" 243 | ): 244 | return 245 | await asyncio.sleep(1) 246 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | ### Current Version (2018-09-14) 4 | * Add Pyppeteer 0.0.24 to requirements 5 | 6 | ### Version 1.8.7 (2018-09-14) 7 | * Bug fix 8 | 9 | ### Version 1.8.6 (2018-09-14) 10 | * Remove Websocket debugger 11 | 12 | ### Version 1.8.5 (2018-09-14) 13 | * Output errors using traceback during solver initialization 14 | 15 | ### Version 1.8.4 (2018-09-14) 16 | * Output errors during solver initialization 17 | * Catch additional errors during page load 18 | * Revert back to opening new tab 19 | 20 | ### Version 1.8.3 (2018-09-10) 21 | * Add option to block images by setting block_images in configuration file. 22 | * Return result to logging, for example "Result: Success", "Result: Blocked" 23 | * Some behind the scene changes. 24 | 25 | ### Version 1.8.2 (2018-09-05) 26 | * requirements.txt 27 | * Update to include Pyppeteer v0.0.21 (Whoops) 28 | 29 | ### Version 1.8.1 (2018-09-05) 30 | * Move exceptions to a separate module - exceptions.py 31 | * solver.py 32 | * Place some long running coroutines into AbastractEventLoop.create_task 33 | * No longer handling BaseExceptions on initial solve method 34 | * launcher.py 35 | * Add modifications for Pyppeteer v0.0.21 36 | * proxy.py 37 | * Replace asyncio.Lock with threading.Lock 38 | * examples/ 39 | * aiohttp_executor.py 40 | * Polished multithreading, ensuring browser exits (hopefully) 41 | * Once again might have forgot something... 42 | 43 | ### Version 1.8.0 (2018-08-06) 44 | * solver.py 45 | * Add function cleanup() to solver for closing browsers 46 | * Bypass Content-Security-Policy in response headers 47 | * launcher.py 48 | * Remove signal handlers in launcher due to redundancy 49 | * proxy.py 50 | * Remove last_used_timeout argument 51 | * examples/ 52 | * Change naming of files 53 | * api.py 54 | * Add multi-threaded support 55 | * Fix bugs 56 | * I might have forgot a change... 57 | 58 | ### Version 1.7.11 (2018-07-25) 59 | * Add compatiblity for Python versions 3.6.0 - 3.7.0 60 | 61 | ### Version 1.7.10 (2018-07-18) 62 | * Fix bug 63 | 64 | ### Version 1.7.9 (2018-07-17) 65 | * Move configuration checking out of __init__.py into base.py 66 | 67 | ### Version 1.7.8 (2018-07-17) 68 | * Remove proxy settings from configuration file 69 | * Remove proxy protocol attribute from Solver (Aiohttp only supports HTTP) 70 | * Fix proxy authentication when downloading audio file 71 | * Add flake8 for auto-testing in repository 72 | 73 | ### Version 1.7.7 (2018-07-10) 74 | * Fix new Chromium update with Pyppeteer 0.0.19 75 | 76 | ### Version 1.7.6 (2018-07-10) 77 | * Fix check_detection timeout 78 | 79 | ### Version 1.7.5 (2018-07-10) 80 | * Fix importing of non-existent/removed Exceptions 81 | 82 | ### Version 1.7.4 (2018-07-08) 83 | * Change the way results are handled 84 | * Success will return in dict {'status': 'success', 'code': CAPTCHACODE} 85 | * Detected will return in dict {'status': 'detected'} 86 | * Max audio retries will return in dict {'status': 'retries_exceeded'} 87 | * Set audio garabage removal to /3.1 88 | * Add browser hang patches from Pyppeteer's repo 89 | 90 | ### Version 1.7.3 (2018-07-08) 91 | * Fix nonocaptcha.example.yaml keys 92 | 93 | ### Version 1.7.2 (2018-07-08) 94 | * Remove APSW dependency in Proxy database for Windows compatibility 95 | 96 | ### Version 1.7.1 (2018-07-08) 97 | * Fix nonocaptcha.example.yaml inclusion 98 | 99 | ### Version 1.7.0 (2018-07-08) 100 | * Add proxy management 101 | * example usage is provided in examples/run.py 102 | * solver.py & audio.py 103 | * Add comments line by line 104 | * Fix bugs 105 | 106 | ### Version 1.6.0 (2018-07-04) 107 | * Switch configuration file to YAML format 108 | * Clean-up requirements.txt 109 | * Downgrade back to pyppeteer 0.0.17 due to frame issues 110 | 111 | ### Version 1.5.8 (2018-07-04) 112 | * Fix bugs 113 | * Update requirements 114 | 115 | ### Version 1.5.7 (2018-07-03) 116 | * Fix bugs 117 | 118 | ### Version 1.5.6 (2018-07-03) 119 | * speech.py 120 | * Remove playback left behind from debugging 121 | 122 | ### Version 1.5.5 (2018-07-03) 123 | * audio.py 124 | * Fix change from InvalidDownload to DownloadError 125 | 126 | ### Verison 1.5.4 (2018-07-03) 127 | * solver.py 128 | * Fix typo on DefaceError 129 | 130 | ### Verison 1.5.3 (2018-07-03) 131 | * Fix bugs 132 | 133 | ### Verison 1.5.2 (2018-07-03) 134 | * requirements.txt 135 | * Remove deepspeech since it makes Windows install fail 136 | 137 | ### Verison 1.5.1 (2018-07-03) 138 | * solver.py 139 | * Revert back to documentloaded 140 | * Don't open a new tab 141 | 142 | ### Verison 1.5.0 (2018-07-02) 143 | * Add support for Mozilla's DeepSpeech 144 | * solver.py 145 | * Deface as soon as page loads except instead waiting for document 146 | * Fix bugs 147 | 148 | ### Verison 1.4.23 (2018-07-02) 149 | * Made more adjustments to the way exits are handled 150 | * Resolutions.json is deprecated, update your configs 151 | * solver.py 152 | * Removed OK| before the reCAPTCHA solution 153 | * data/ 154 | * Update deface.html with nonoCAPTCHA title 155 | 156 | ### Verison 1.4.22 (2018-07-02) 157 | * launcher.py 158 | * Fix Exception thrown while killing non-existent process 159 | 160 | ### Verison 1.4.22 (2018-07-02) 161 | * launcher.py 162 | * Fix typo in kill process 163 | 164 | ### Verison 1.4.21 (2018-07-02) 165 | * Fix bugs 166 | 167 | ### Verison 1.4.19 (2018-07-02) 168 | * speech.py 169 | * Fix mp3_to_wav() 170 | 171 | ### Verison 1.4.18 (2018-07-02) 172 | * Add requests to requirements.txt 173 | 174 | ### Verison 1.4.17 (2018-07-02) 175 | * Increase polling to 500ms for detection checking 176 | * Recursively kill child processes of Chrome 177 | 178 | ### Verison 1.4.16 (2018-07-01) 179 | * Fix audio downloading and file saving in Windows 180 | * Pipe PocketSphinx logs to NUL under Windows 181 | * Decrease polling to 100ms for detection checking 182 | 183 | ### Version 1.4.15 (2018-07-01) 184 | * Attempt to fix issues with ongoing issue with Windows directory removal 185 | * Possible fix for rare hanging on close 186 | * More redifinition of exception handling 187 | 188 | ### Version 1.4.14 (2018-07-01) 189 | * Redefine names of thrown exceptions better suited for invidual cases 190 | * Fix bugs 191 | 192 | ### Version 1.4.13 (2018-06-30) 193 | * Fix issues with Windows directory removal 194 | 195 | ### Version 1.4.12 (2018-06-30) 196 | * Remove remove_readonly 197 | 198 | ### Version 1.4.11 (2018-06-30) 199 | * Place subprocess into list for killing parent Chrome 200 | 201 | ### Version 1.4.10 (2018-06-30) 202 | * Actually 'import subprocess' 203 | 204 | ### Version 1.4.9 (2018-06-30) 205 | * launcher..py 206 | * 'import subprocess' 207 | 208 | ### Version 1.4.8 (2018-06-30) 209 | * Kill parent Chromium process in Windows to allow deletion of Temporary User Data 210 | * Fix Google login 211 | * audio.py 212 | * Add 'import asyncio' 213 | * solver.py 214 | * remove self.kill_chrome 215 | * Fix bugs 216 | 217 | ### Version 1.4.7 (2018-06-30) 218 | * Fix bugs 219 | 220 | ### Version 1.4.6 (2018-06-30) 221 | * util.py 222 | * Fix aiohttp missing Timeout outside it's scope 223 | * examples/app.py 224 | * Now uses aiohttp instead of Quart 225 | 226 | ### Version 1.4.5 (2018-06-29) 227 | * Sphinx module 228 | * Strip static by percentage instead of 1500ms 229 | * Audio solving 230 | * Fix "Please solve more" bug, where it would exit instead of trying again 231 | 232 | ### Version 1.4.4 (2018-06-29) 233 | * Sphinx module 234 | * Strip static from audio files 235 | * Remove extra spaces from middle of words 236 | 237 | ### Version 1.4.3 (2018-06-29) 238 | * Sphinx module 239 | * Remove detect silence 240 | 241 | ### Version 1.4.2 (2018-06-29) 242 | * Fix bugs 243 | 244 | ### Version 1.4.1 (2018-06-29) 245 | * Remove yet another print.. 246 | 247 | ### Version 1.4.0 (2018-06-29) 248 | * Add support for PocketSphinx 249 | 250 | ### Version 1.3.3 (2018-06-29) 251 | * Actually remove a print function.. 252 | 253 | ### Version 1.3.2 (2018-06-29) 254 | * Remove a print function.. 255 | 256 | ### Version 1.3.1 (2018-06-29) 257 | * Fix Azure Speech-to-text 258 | 259 | ### Version 1.3.0 (2018-06-28) 260 | * Add support for Amazon's Transcribe Speech-to-text 261 | 262 | ### Version 1.2.10 (2018-06-28) 263 | * Fix bugs 264 | 265 | ### Version 1.2.9 (2018-06-27) 266 | * Delete temporary Chrome profile on Browser exit 267 | 268 | ### Version 1.2.8 (2018-06-27) 269 | * Possible fix for Chrome termination on ungraceful exit (such as timeout) 270 | 271 | ### Version 1.2.7 (2018-06-27) 272 | * Revert back to old reCAPTCHA loading method 273 | 274 | ### Version 1.2.6 (2018-06-26) 275 | * Remove chrome arguments uncertatin of their purpose 276 | * Remove hardcoded timeout from solver, handle externally 277 | * Add new example for HTTP client - create_task / get_task 278 | 279 | ### Version 1.2.5 (2018-06-26) 280 | * Fix bugs 281 | 282 | ### Version 1.2.4 (2018-06-26) 283 | * Timeout patch in solver reverted 284 | 285 | ### Version 1.2.3 (2018-06-25) 286 | * Add CHANGES.md file 287 | * Add TODO.md file 288 | * Lower mouse click (30ms,130ms) and wait delay(500ms,1.5secs) 289 | 290 | ### ... unfinished 291 | 292 | ### Version 0.0.14 (2018-06-20) 293 | * Fix bugs 294 | 295 | ### Version 0.0.13 (2018-06-20) 296 | * Fix bugs 297 | 298 | ### Version 0.0.12 (2018-06-20) 299 | * nonocaptcha/util.py 300 | * Increase get_page default timeout to 5 minutes 301 | * Add config.py missing warning 302 | * Fix bugs 303 | 304 | ### Version 0.0.11 (2018-06-20) 305 | * Fix bugs 306 | 307 | ### Version 0.0.10 (2018-06-20) 308 | 309 | * data/ 310 | * Move to package directory 311 | * examples/ 312 | * app.py 313 | * Proxies load in packground with 10 minute interval 314 | * setup.py 315 | * Add Github url 316 | * Rename package to be all lowercase 317 | * Register PyPI 318 | * Keep count of tasks in logging 319 | * Fix bugs 320 | 321 | ### Version 0.0.9 (2018-06-20) 322 | 323 | * data/ 324 | * Add cookie_jar directory 325 | * config.py to work from current directory 326 | * Add new presentation 327 | * Option to sign-in to single Google account 328 | * Fix bugs 329 | 330 | ### Version 0.0.6 (2018-06-19) 331 | 332 | * Distribution 333 | * Script can now be installed with setup.py 334 | * config.example.py 335 | * Blacklist setting added 336 | * examples/ 337 | * run.py 338 | * Parallel continous browsing 339 | * Log use logging module 340 | * Async subprocess calls browser 341 | * Add Extra chrome arguments for less tracking 342 | * Option to check Google search for blacklist heuristic 343 | * Fix bugs 344 | 345 | ### Version 0.0.3 (2018-06-17) 346 | 347 | * README.md 348 | * Added Compatibility section 349 | * Updated Requirements to include FFmpeg 350 | * config.example.py 351 | * Added debug setting 352 | * Lowered success_timeout to 5 seconds 353 | 354 | ### Version 0.0.2 (2018-06-15) 355 | 356 | * README.md 357 | * Added Displaimer section 358 | * Added presentation GIF 359 | * Code formatting with black 360 | 361 | 362 | ### Version 0.0.1 (2018-06-14) 363 | 364 | * Released to Githib 365 | -------------------------------------------------------------------------------- /nonocaptcha/solver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Solver module. """ 5 | 6 | import asyncio 7 | import json 8 | import sys 9 | import time 10 | import traceback 11 | 12 | from pyppeteer.util import merge_dict 13 | from user_agent import generate_navigator_js 14 | 15 | from nonocaptcha.base import Base 16 | from nonocaptcha.audio import SolveAudio 17 | from nonocaptcha.image import SolveImage 18 | from nonocaptcha.launcher import Launcher 19 | from nonocaptcha import util 20 | from nonocaptcha.exceptions import (SafePassage, ButtonError, DefaceError, 21 | PageError) 22 | 23 | 24 | class Solver(Base): 25 | browser = None 26 | launcher = None 27 | proc_count = 0 28 | proc = None 29 | 30 | def __init__( 31 | self, 32 | pageurl, 33 | sitekey, 34 | loop=None, 35 | proxy=None, 36 | proxy_auth=None, 37 | options={}, 38 | **kwargs, 39 | ): 40 | self.options = merge_dict(options, kwargs) 41 | self.url = pageurl 42 | self.sitekey = sitekey 43 | self.loop = loop or asyncio.get_event_loop() 44 | self.proxy = f"http://{proxy}" if proxy else proxy 45 | self.proxy_auth = proxy_auth 46 | self.proc_id = self.proc_count 47 | type(self).proc_count += 1 48 | 49 | async def start(self): 50 | """Begin solving""" 51 | start = time.time() 52 | result = None 53 | try: 54 | self.browser = await self.get_new_browser() 55 | self.page = await self.browser.newPage() 56 | if self.should_block_images: 57 | await self.page.setRequestInterception(True) 58 | self.block_images() 59 | if self.proxy_auth: 60 | await self.page.authenticate(self.proxy_auth) 61 | self.log(f"Starting solver with proxy {self.proxy}") 62 | await self.set_bypass_csp() 63 | await self.goto() 64 | await self.deface() 65 | result = await self.solve() 66 | except BaseException as e: 67 | print(traceback.format_exc()) 68 | self.log(f"{e} {type(e)}") 69 | except Exception as e: 70 | traceback.print_exc(file=sys.stdout) 71 | raise e 72 | finally: 73 | if isinstance(result, dict): 74 | status = result['status'].capitalize() 75 | self.log(f"Result: {status}") 76 | end = time.time() 77 | elapsed = end - start 78 | await self.cleanup() 79 | self.log(f"Time elapsed: {elapsed}") 80 | return result 81 | 82 | def block_images(self): 83 | async def handle_request(request): 84 | if (request.resourceType == 'image'): 85 | await request.abort() 86 | else: 87 | await request.continue_() 88 | 89 | self.page.on('request', handle_request) 90 | 91 | async def cleanup(self): 92 | if self.browser: 93 | await self.browser.close() 94 | self.log('Browser closed') 95 | 96 | async def set_bypass_csp(self): 97 | await self.page._client.send( 98 | "Page.setBypassCSP", {'enabled': True}) 99 | 100 | async def get_new_browser(self): 101 | """Get a new browser, set proxy and arguments""" 102 | args = [ 103 | '--cryptauth-http-host ""', 104 | '--disable-accelerated-2d-canvas', 105 | '--disable-background-networking', 106 | '--disable-background-timer-throttling', 107 | '--disable-browser-side-navigation', 108 | '--disable-client-side-phishing-detection', 109 | '--disable-default-apps', 110 | '--disable-dev-shm-usage', 111 | '--disable-device-discovery-notifications', 112 | '--disable-extensions', 113 | '--disable-features=site-per-process', 114 | '--disable-hang-monitor', 115 | '--disable-java', 116 | '--disable-popup-blocking', 117 | '--disable-prompt-on-repost', 118 | '--disable-setuid-sandbox', 119 | '--disable-sync', 120 | '--disable-translate', 121 | '--disable-web-security', 122 | '--disable-webgl', 123 | '--metrics-recording-only', 124 | '--no-first-run', 125 | '--safebrowsing-disable-auto-update', 126 | '--no-sandbox', 127 | # Automation arguments 128 | '--enable-automation', 129 | '--password-store=basic', 130 | '--use-mock-keychain'] 131 | if self.proxy: 132 | args.append(f"--proxy-server={self.proxy}") 133 | if "args" in self.options: 134 | args.extend(self.options.pop("args")) 135 | if "headless" in self.options: 136 | self.headless = self.options["headless"] 137 | self.options.update({ 138 | "headless": self.headless, 139 | "args": args, 140 | # Silence Pyppeteer logs 141 | "logLevel": "CRITICAL"}) 142 | self.launcher = Launcher(self.options) 143 | browser = await self.launcher.launch() 144 | return browser 145 | 146 | async def cloak_navigator(self): 147 | """Emulate another browser's navigator properties and set webdriver 148 | false, inject jQuery. 149 | """ 150 | jquery_js = await util.load_file(self.jquery_data) 151 | override_js = await util.load_file(self.override_data) 152 | navigator_config = generate_navigator_js( 153 | os=("linux", "mac", "win"), navigator=("chrome")) 154 | navigator_config["mediaDevices"] = False 155 | navigator_config["webkitGetUserMedia"] = False 156 | navigator_config["mozGetUserMedia"] = False 157 | navigator_config["getUserMedia"] = False 158 | navigator_config["webkitRTCPeerConnection"] = False 159 | navigator_config["webdriver"] = False 160 | dump = json.dumps(navigator_config) 161 | _navigator = f"const _navigator = {dump};" 162 | await self.page.evaluateOnNewDocument( 163 | "() => {\n%s\n%s\n%s}" % (_navigator, jquery_js, override_js)) 164 | return navigator_config["userAgent"] 165 | 166 | async def wait_for_deface(self): 167 | """Overwrite current page with reCAPTCHA widget and wait for image 168 | iframe to appear on dom before continuing. 169 | 170 | Function x is an odd hack for multiline text, but it works. 171 | """ 172 | html_code = await util.load_file(self.deface_data) 173 | deface_js = ( 174 | ( 175 | """() => { 176 | var x = (function () {/* 177 | %s 178 | */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]; 179 | document.open(); 180 | document.write(x) 181 | document.close(); 182 | } 183 | """ 184 | % html_code) 185 | % self.sitekey) 186 | await self.page.evaluate(deface_js) 187 | func = """() => { 188 | frame = $("iframe[src*='api2/bframe']") 189 | $(frame).load( function() { 190 | window.ready_eddy = true; 191 | }); 192 | if(window.ready_eddy){ 193 | return true; 194 | }else{ 195 | // I don't think this is necessary but it won't hurt... 196 | var evt = document.createEvent('Event'); 197 | evt.initEvent('load', false, false); 198 | window.dispatchEvent(evt); 199 | } 200 | }""" 201 | await self.page.waitForFunction(func, timeout=self.deface_timeout) 202 | 203 | async def goto(self): 204 | """Navigate to address""" 205 | user_agent = await self.cloak_navigator() 206 | await self.page.setUserAgent(user_agent) 207 | try: 208 | await self.loop.create_task( 209 | self.page.goto( 210 | self.url, 211 | timeout=self.page_load_timeout, 212 | waitUntil="domcontentloaded",)) 213 | except asyncio.TimeoutError: 214 | raise PageError("Page loading timed-out") 215 | except Exception as exc: 216 | raise PageError(f"Page raised an error: `{exc}`") 217 | 218 | async def deface(self): 219 | try: 220 | await self.loop.create_task(self.wait_for_deface()) 221 | except asyncio.TimeoutError: 222 | raise DefaceError("Problem defacing page") 223 | 224 | async def solve(self): 225 | """Click checkbox, otherwise attempt to decipher audio""" 226 | await self.get_frames() 227 | await self.loop.create_task(self.wait_for_checkbox()) 228 | await self.click_checkbox() 229 | try: 230 | result = await self.loop.create_task( 231 | self.check_detection(self.animation_timeout)) 232 | except SafePassage: 233 | return await self._solve() 234 | else: 235 | if result["status"] == "success": 236 | code = await self.g_recaptcha_response() 237 | if code: 238 | result["code"] = code 239 | return result 240 | else: 241 | return result 242 | 243 | async def _solve(self): 244 | # Coming soon... 245 | solve_image = True 246 | if solve_image: 247 | self.image = SolveImage( 248 | self.browser, 249 | self.image_frame, 250 | self.proxy, 251 | self.proxy_auth, 252 | self.proc_id) 253 | solve = self.image.solve_by_image 254 | else: 255 | self.audio = SolveAudio( 256 | self.page, 257 | self.loop, 258 | self.proxy, 259 | self.proxy_auth, 260 | self.proc_id) 261 | await self.loop.create_task(self.wait_for_audio_button()) 262 | result = await self.click_audio_button() 263 | if isinstance(result, dict): 264 | if result["status"] == "detected": 265 | return result 266 | solve = self.audio.solve_by_audio 267 | 268 | result = await self.loop.create_task(solve()) 269 | if result["status"] == "success": 270 | code = await self.g_recaptcha_response() 271 | if code: 272 | result["code"] = code 273 | return result 274 | else: 275 | return result 276 | 277 | async def wait_for_checkbox(self): 278 | """Wait for checkbox to appear.""" 279 | try: 280 | await self.checkbox_frame.waitForFunction( 281 | "$('#recaptcha-anchor').length", 282 | timeout=self.animation_timeout) 283 | except ButtonError: 284 | raise ButtonError("Checkbox missing, aborting") 285 | 286 | async def click_checkbox(self): 287 | """Click checkbox on page load.""" 288 | if self.keyboard_traverse: 289 | self.body = await self.page.J("body") 290 | await self.body.press("Tab") 291 | await self.body.press("Enter") 292 | else: 293 | self.log("Clicking checkbox") 294 | checkbox = await self.checkbox_frame.J("#recaptcha-anchor") 295 | await self.click_button(checkbox) 296 | 297 | async def wait_for_audio_button(self): 298 | """Wait for audio button to appear.""" 299 | try: 300 | await self.image_frame.waitForFunction( 301 | "$('#recaptcha-audio-button').length", 302 | timeout=self.animation_timeout) 303 | except ButtonError: 304 | raise ButtonError("Audio button missing, aborting") 305 | 306 | async def click_audio_button(self): 307 | """Click audio button after it appears.""" 308 | if self.keyboard_traverse: 309 | await self.body.press("Enter") 310 | else: 311 | self.log("Clicking audio button") 312 | audio_button = await self.image_frame.J("#recaptcha-audio-button") 313 | await self.click_button(audio_button) 314 | try: 315 | result = await self.check_detection(self.animation_timeout) 316 | except SafePassage: 317 | pass 318 | else: 319 | return result 320 | 321 | async def g_recaptcha_response(self): 322 | code = await self.page.evaluate("$('#g-recaptcha-response').val()") 323 | return code 324 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------