├── 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 |
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 |
--------------------------------------------------------------------------------