diff --git a/README.md b/README.md index 86fbb21..3321d1e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Examples of API requests for different captcha types are available on the [Pytho - [Alibaba](#alibaba) - [TSPD](#tspd) - [Basilisk](#basilisk) + - [Drag & Drop](#drag--drop) - [Other methods](#other-methods) - [send / get\_result](#send--get_result) - [balance](#balance) @@ -611,6 +612,26 @@ result = solver.basilisk(pageurl='https://example.com/login', ) ``` +### Drag & Drop + +[API method description.](https://2captcha.com/2captcha-api#drag-and-drop-captcha) + +Use this method to solve a captcha where one or more images need to be dragged onto specific positions on a background image. +`body` and each item in `images` can be a file path, a URL, or a Base64-encoded string. + +Returns the standard result dictionary `{'captchaId': 'TASK_ID', 'code': 'COORDINATES'}`. The coordinates string in +`result['code']` contains one entry per image from `images`, in the same order, separated by `|`. An image that +wasn't moved may be returned by the API as `null`. + +```python +result = solver.drag_and_drop( + body='path/to/background.jpg', + images=['path/to/image1.jpg', 'path/to/image2.jpg'], +) + +coordinates = result['code'] +``` + ## Other methods ### send / get_result diff --git a/examples/async/async_drag_and_drop.py b/examples/async/async_drag_and_drop.py new file mode 100644 index 0000000..af95f8f --- /dev/null +++ b/examples/async/async_drag_and_drop.py @@ -0,0 +1,30 @@ +import asyncio +import os +import sys + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import AsyncTwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = AsyncTwoCaptcha(api_key) + + +async def solve_captcha(): + try: + return await solver.drag_and_drop(body='../images/drag_drop_main.jpeg', + images=['../images/drag_drop_image1.jpeg', '../images/drag_drop_image2.jpeg']) + except Exception as e: + sys.exit(e) + + +if __name__ == '__main__': + result = asyncio.run(solve_captcha()) + sys.exit('result: ' + str(result)) diff --git a/examples/async/async_drag_and_drop_base64.py b/examples/async/async_drag_and_drop_base64.py new file mode 100644 index 0000000..9eb065c --- /dev/null +++ b/examples/async/async_drag_and_drop_base64.py @@ -0,0 +1,42 @@ +import asyncio +import os +import sys +from base64 import b64encode + +import aiofiles + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import AsyncTwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = AsyncTwoCaptcha(api_key) + + +async def solve_captcha(): + async with aiofiles.open('../images/drag_drop_main.jpeg', 'rb') as f: + body_b64 = b64encode(await f.read()).decode('utf-8') + + async with aiofiles.open('../images/drag_drop_image1.jpeg', 'rb') as f: + image1_b64 = b64encode(await f.read()).decode('utf-8') + + async with aiofiles.open('../images/drag_drop_image2.jpeg', 'rb') as f: + image2_b64 = b64encode(await f.read()).decode('utf-8') + + try: + return await solver.drag_and_drop(body=body_b64, + images=[image1_b64, image2_b64]) + except Exception as e: + sys.exit(e) + + +if __name__ == '__main__': + result = asyncio.run(solve_captcha()) + sys.exit('result: ' + str(result)) diff --git a/examples/async/async_drag_and_drop_options.py b/examples/async/async_drag_and_drop_options.py new file mode 100644 index 0000000..fc1368f --- /dev/null +++ b/examples/async/async_drag_and_drop_options.py @@ -0,0 +1,36 @@ +import asyncio +import os +import sys + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import AsyncTwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = AsyncTwoCaptcha(api_key, defaultTimeout=120, pollingInterval=10) + + +async def solve_captcha(): + try: + return await solver.drag_and_drop( + body='../images/drag_drop_main.jpeg', + images=['../images/drag_drop_image1.jpeg', '../images/drag_drop_image2.jpeg'], + hintText='Drag the images to proper position', + language=0, + lang='en', + header_acao=0, + ) + except Exception as e: + sys.exit(e) + + +if __name__ == '__main__': + result = asyncio.run(solve_captcha()) + sys.exit('result: ' + str(result)) diff --git a/examples/images/drag_drop_image1.jpeg b/examples/images/drag_drop_image1.jpeg new file mode 100644 index 0000000..f1bb604 Binary files /dev/null and b/examples/images/drag_drop_image1.jpeg differ diff --git a/examples/images/drag_drop_image2.jpeg b/examples/images/drag_drop_image2.jpeg new file mode 100644 index 0000000..86ed7a8 Binary files /dev/null and b/examples/images/drag_drop_image2.jpeg differ diff --git a/examples/images/drag_drop_main.jpeg b/examples/images/drag_drop_main.jpeg new file mode 100644 index 0000000..675ba0d Binary files /dev/null and b/examples/images/drag_drop_main.jpeg differ diff --git a/examples/sync/drag_and_drop.py b/examples/sync/drag_and_drop.py new file mode 100644 index 0000000..ad6e310 --- /dev/null +++ b/examples/sync/drag_and_drop.py @@ -0,0 +1,26 @@ +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import TwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = TwoCaptcha(api_key) + +try: + result = solver.drag_and_drop(body='../images/drag_drop_main.jpeg', + images=['../images/drag_drop_image1.jpeg', '../images/drag_drop_image2.jpeg']) + +except Exception as e: + sys.exit(str(e)) + +else: + sys.exit('result: ' + str(result)) diff --git a/examples/sync/drag_and_drop_base64.py b/examples/sync/drag_and_drop_base64.py new file mode 100644 index 0000000..78ecf63 --- /dev/null +++ b/examples/sync/drag_and_drop_base64.py @@ -0,0 +1,36 @@ +import sys +import os +from base64 import b64encode + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import TwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = TwoCaptcha(api_key) + +with open('../images/drag_drop_main.jpeg', 'rb') as f: + body_b64 = b64encode(f.read()).decode('utf-8') + +with open('../images/drag_drop_image1.jpeg', 'rb') as f: + image1_b64 = b64encode(f.read()).decode('utf-8') + +with open('../images/drag_drop_image2.jpeg', 'rb') as f: + image2_b64 = b64encode(f.read()).decode('utf-8') + +try: + result = solver.drag_and_drop(body=body_b64, + images=[image1_b64, image2_b64]) + +except Exception as e: + sys.exit(str(e)) + +else: + sys.exit('result: ' + str(result)) diff --git a/examples/sync/drag_and_drop_options.py b/examples/sync/drag_and_drop_options.py new file mode 100644 index 0000000..026f925 --- /dev/null +++ b/examples/sync/drag_and_drop_options.py @@ -0,0 +1,32 @@ +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) + +from twocaptcha import TwoCaptcha + +# in this example we store the API key inside environment variables that can be set like: +# export APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS +# set APIKEY_2CAPTCHA=1abc234de56fab7c89012d34e56fa7b8 on Windows +# you can just set the API key directly to it's value like: +# api_key="1abc234de56fab7c89012d34e56fa7b8" + +api_key = os.getenv('APIKEY_2CAPTCHA', 'YOUR_API_KEY') + +solver = TwoCaptcha(api_key, defaultTimeout=120, pollingInterval=10) + +try: + result = solver.drag_and_drop( + body='../images/drag_drop_main.jpeg', + images=['../images/drag_drop_image1.jpeg', '../images/drag_drop_image2.jpeg'], + hintText='Drag the images to proper position', + language=0, + lang='en', + header_acao=0, + ) + +except Exception as e: + sys.exit(str(e)) + +else: + sys.exit('result: ' + str(result)) diff --git a/setup.py b/setup.py index 661127c..9b63658 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,6 @@ def get_version(): '2captcha', 'captcha', 'api', 'captcha solver', 'reCAPTCHA', 'FunCaptcha', 'Geetest', 'image captcha', 'Coordinates', 'Click Captcha', 'Geetest V4', 'Lemin captcha', 'Amazon WAF', 'Cloudflare Turnstile', - 'Capy Puzzle', 'MTCaptcha', 'Friendly Captcha', 'Tencent', 'Cutcaptcha', 'DataDome', 'VK Captcha', 'CaptchaFox', 'Prosopo', 'cybersiara', 'Hunt', 'Alibaba', 'TSPD', 'Basilisk'], + 'Capy Puzzle', 'MTCaptcha', 'Friendly Captcha', 'Tencent', 'Cutcaptcha', 'DataDome', 'VK Captcha', 'CaptchaFox', 'Prosopo', 'cybersiara', 'Hunt', 'Alibaba', 'TSPD', 'Basilisk', 'Drag & Drop'], python_requires='>=3.8', test_suite='tests') diff --git a/tests/async/test_async_drag_and_drop.py b/tests/async/test_async_drag_and_drop.py new file mode 100644 index 0000000..e7ca646 --- /dev/null +++ b/tests/async/test_async_drag_and_drop.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +import asyncio +import json +import unittest +from base64 import b64encode +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch + +images_path = Path(__file__).resolve().parents[2] / 'examples' / 'images' +background = str(images_path / 'canvas.jpg') +image1 = str(images_path / 'grid.jpg') +image2 = str(images_path / 'normal.jpg') + +with open(image1, 'rb') as fh: + image1_b64 = b64encode(fh.read()).decode('utf-8') + +with open(image2, 'rb') as fh: + image2_b64 = b64encode(fh.read()).decode('utf-8') + +with open(background, 'rb') as fh: + background_b64 = b64encode(fh.read()).decode('utf-8') + +background_url = 'https://example.com/background.jpg' +image1_url = 'https://example.com/image1.jpg' +image2_url = 'https://example.com/image2.jpg' + +background_url_bytes = b'background-url-bytes' +image1_url_bytes = b'image1-url-bytes' +image2_url_bytes = b'image2-url-bytes' + +background_url_b64 = b64encode(background_url_bytes).decode('utf-8') +image1_url_b64 = b64encode(image1_url_bytes).decode('utf-8') +image2_url_b64 = b64encode(image2_url_bytes).decode('utf-8') + +URL_CONTENT = { + background_url: background_url_bytes, + image1_url: image1_url_bytes, + image2_url: image2_url_bytes, +} + + +def fake_get(url, status_code=200): + if url not in URL_CONTENT: + raise AssertionError(f'unexpected url requested: {url}') + return MagicMock(status_code=status_code, content=URL_CONTENT[url]) + + +try: + from .abstract_async import AsyncAbstractTest +except ImportError: + from abstract_async import AsyncAbstractTest + + +class AsyncDragAndDrop(AsyncAbstractTest): + + def test_files(self): + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + } + + self.send_return(sends, self.solver.drag_and_drop, + body=background, images=[image1, image2]) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_base64(self): + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + } + + self.send_return(sends, self.solver.drag_and_drop, + body=background_b64, images=[image1_b64, image2_b64]) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_url(self): + sends = { + 'method': 'drag_drop', + 'body': background_url_b64, + 'images': json.dumps([image1_url_b64, image2_url_b64]), + } + + with patch('httpx.AsyncClient.get', new_callable=AsyncMock, side_effect=fake_get) as mock_get: + self.send_return(sends, self.solver.drag_and_drop, + body=background_url, images=[image1_url, image2_url]) + + mock_get.assert_any_call(background_url) + mock_get.assert_any_call(image1_url) + mock_get.assert_any_call(image2_url) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_images_order_preserved_with_mixed_sources(self): + fake_b64_string = 'B' * 60 + + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_url_b64, image2_b64, fake_b64_string]), + } + + with patch('httpx.AsyncClient.get', new_callable=AsyncMock, side_effect=fake_get): + self.send_return(sends, self.solver.drag_and_drop, + body=background, images=[image1_url, image2, fake_b64_string]) + + def test_all_params(self): + params = { + 'body': background, + 'images': [image1, image2], + 'hintText': 'Drag the images to proper position', + 'language': 0, + 'lang': 'en', + 'header_acao': 0, + } + + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + 'textinstructions': 'Drag the images to proper position', + 'language': 0, + 'lang': 'en', + 'header_acao': 0, + } + + self.send_return(sends, self.solver.drag_and_drop, **params) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_body_not_found(self): + self.invalid_file(self.solver.drag_and_drop, images=[image1]) + + def test_image_not_found(self): + with self.assertRaises(self.solver.exceptions): + asyncio.run(self.solver.drag_and_drop(background, ['lost_file'])) + + def test_body_url_download_failure(self): + with patch('httpx.AsyncClient.get', new_callable=AsyncMock, + side_effect=lambda url: fake_get(url, status_code=404)): + with self.assertRaises(self.solver.exceptions): + asyncio.run(self.solver.drag_and_drop(background_url, [image1])) + + def test_image_url_download_failure(self): + with patch('httpx.AsyncClient.get', new_callable=AsyncMock, + side_effect=lambda url: fake_get(url, status_code=500)): + with self.assertRaises(self.solver.exceptions): + asyncio.run(self.solver.drag_and_drop(background, [image1_url])) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/sync/test_drag_and_drop.py b/tests/sync/test_drag_and_drop.py new file mode 100644 index 0000000..258968e --- /dev/null +++ b/tests/sync/test_drag_and_drop.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 + +import json +import unittest +from base64 import b64encode +from pathlib import Path +from unittest.mock import MagicMock, patch + +images_path = Path(__file__).resolve().parents[2] / 'examples' / 'images' +background = str(images_path / 'canvas.jpg') +image1 = str(images_path / 'grid.jpg') +image2 = str(images_path / 'normal.jpg') + +with open(image1, 'rb') as fh: + image1_b64 = b64encode(fh.read()).decode('utf-8') + +with open(image2, 'rb') as fh: + image2_b64 = b64encode(fh.read()).decode('utf-8') + +with open(background, 'rb') as fh: + background_b64 = b64encode(fh.read()).decode('utf-8') + +background_url = 'https://example.com/background.jpg' +image1_url = 'https://example.com/image1.jpg' +image2_url = 'https://example.com/image2.jpg' + +background_url_bytes = b'background-url-bytes' +image1_url_bytes = b'image1-url-bytes' +image2_url_bytes = b'image2-url-bytes' + +background_url_b64 = b64encode(background_url_bytes).decode('utf-8') +image1_url_b64 = b64encode(image1_url_bytes).decode('utf-8') +image2_url_b64 = b64encode(image2_url_bytes).decode('utf-8') + +URL_CONTENT = { + background_url: background_url_bytes, + image1_url: image1_url_bytes, + image2_url: image2_url_bytes, +} + + +def fake_get(url, status_code=200): + if url not in URL_CONTENT: + raise AssertionError(f'unexpected url requested: {url}') + return MagicMock(status_code=status_code, content=URL_CONTENT[url]) + + +try: + from .abstract import AbstractTest +except ImportError: + from abstract import AbstractTest + + +class DragAndDrop(AbstractTest): + + def test_files(self): + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + } + + self.send_return(sends, self.solver.drag_and_drop, + body=background, images=[image1, image2]) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_base64(self): + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + } + + self.send_return(sends, self.solver.drag_and_drop, + body=background_b64, images=[image1_b64, image2_b64]) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_url(self): + sends = { + 'method': 'drag_drop', + 'body': background_url_b64, + 'images': json.dumps([image1_url_b64, image2_url_b64]), + } + + with patch('twocaptcha.solver.requests.get', side_effect=fake_get) as mock_get: + self.send_return(sends, self.solver.drag_and_drop, + body=background_url, images=[image1_url, image2_url]) + + mock_get.assert_any_call(background_url) + mock_get.assert_any_call(image1_url) + mock_get.assert_any_call(image2_url) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_images_order_preserved_with_mixed_sources(self): + fake_b64_string = 'B' * 60 + + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_url_b64, image2_b64, fake_b64_string]), + } + + with patch('twocaptcha.solver.requests.get', side_effect=fake_get): + self.send_return(sends, self.solver.drag_and_drop, + body=background, images=[image1_url, image2, fake_b64_string]) + + def test_all_params(self): + params = { + 'body': background, + 'images': [image1, image2], + 'hintText': 'Drag the images to proper position', + 'language': 0, + 'lang': 'en', + 'header_acao': 0, + } + + sends = { + 'method': 'drag_drop', + 'body': background_b64, + 'images': json.dumps([image1_b64, image2_b64]), + 'textinstructions': 'Drag the images to proper position', + 'language': 0, + 'lang': 'en', + 'header_acao': 0, + } + + self.send_return(sends, self.solver.drag_and_drop, **params) + self.assertNotIn('file', self.solver.api_client.incomings) + + def test_body_not_found(self): + return self.invalid_file(self.solver.drag_and_drop, images=[image1]) + + def test_image_not_found(self): + self.assertRaises(self.solver.exceptions, self.solver.drag_and_drop, + background, ['lost_file']) + + def test_body_url_download_failure(self): + with patch('twocaptcha.solver.requests.get', + side_effect=lambda url: fake_get(url, status_code=404)): + self.assertRaises(self.solver.exceptions, self.solver.drag_and_drop, + background_url, [image1]) + + def test_image_url_download_failure(self): + with patch('twocaptcha.solver.requests.get', + side_effect=lambda url: fake_get(url, status_code=500)): + self.assertRaises(self.solver.exceptions, self.solver.drag_and_drop, + background, [image1_url]) + + +if __name__ == '__main__': + unittest.main() diff --git a/twocaptcha/async_solver.py b/twocaptcha/async_solver.py index 486d1f7..055d7fc 100644 --- a/twocaptcha/async_solver.py +++ b/twocaptcha/async_solver.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import asyncio +import json import os import sys import time @@ -1186,6 +1187,63 @@ async def basilisk(self, pageurl, sitekey, **kwargs): return await result + async def drag_and_drop(self, body, images, **kwargs): + '''Wrapper for solving Drag & Drop captcha. + + Parameters + __________ + body : str + Background image: file path, URL, or Base64-encoded image string. Always sent to the API as + a Base64-encoded string. + images : list + List of images (file path, URL, or Base64-encoded string) that need to be dragged onto the + background. Order matters: the response contains coordinates in the same order. + hintText : str, optional + Max 140 characters. Encoding: UTF-8. Text with instruction for solving the captcha. For example: + "Drag the images to proper position". + language : int, optional + 0 - not specified. 1 - Cyrillic captcha. 2 - Latin captcha. + Default: 0. + lang : str, optional + Language code. See the list of supported languages https://2captcha.com/2captcha-api#language. + header_acao : int, optional + 0 - disabled. 1 - enabled. If enabled in.php will include Access-Control-Allow-Origin:* header in + the response. + Default: 0. + softId : int, optional + ID of software developer. Developers who integrated their software with 2Captcha get reward: 10% of + spendings of their software users. + callback : str, optional + URL for pingback (callback) response that will be sent when captcha is solved. URL should be registered on + the server. More info here https://2captcha.com/2captcha-api#pingback. + + Returns + _______ + dict + {'captchaId': 'TASK_ID', 'code': 'COORDINATES'}. The coordinates string in `result['code']` contains + one entry per image from `images`, in the same order, separated by "|". An image that wasn't moved + may be returned by the API as "null". + ''' + + async def to_base64(image): + image_method = await self.get_method(image) + if 'body' in image_method: + return image_method['body'] + async with aiofiles.open(image_method['file'], 'rb') as img: + file_content = await img.read() + return b64encode(file_content).decode('utf-8') + + body_b64 = await to_base64(body) + images_b64 = [await to_base64(image) for image in images] + + result = self.solve( + method='drag_drop', + body=body_b64, + images=json.dumps(images_b64), + **kwargs) + + return await result + async def solve(self, timeout=0, polling_interval=0, **kwargs): '''Sends captcha, receives result. diff --git a/twocaptcha/solver.py b/twocaptcha/solver.py index 2410d73..3067df0 100755 --- a/twocaptcha/solver.py +++ b/twocaptcha/solver.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import json import os import sys import time @@ -121,6 +122,8 @@ class TwoCaptcha(): Wrapper for solving TSPD captcha. basilisk(self, pageurl, sitekey, **kwargs) Wrapper for solving Basilisk captcha. + drag_and_drop(self, body, images, **kwargs) + Wrapper for solving Drag & Drop captcha. Wrapper for solving Drag & Drop captcha. Returns a result dictionary with coordinates in `code`. solve(timeout=0, polling_interval=0, **kwargs) Sends CAPTCHA data and retrieves the result. balance() @@ -1336,6 +1339,59 @@ def basilisk(self, pageurl, sitekey, **kwargs): return result + def drag_and_drop(self, body, images, **kwargs): + '''Wrapper for solving Drag & Drop captcha. + + Parameters + __________ + body : str + Background image: file path, URL, or Base64-encoded image string. Always sent to the API as + a Base64-encoded string. + images : list + List of images (file path, URL, or Base64-encoded string) that need to be dragged onto the + background. Order matters: the response contains coordinates in the same order. + hintText : str, optional + Max 140 characters. Encoding: UTF-8. Text with instruction for solving the captcha. For example: + "Drag the images to proper position". + language : int, optional + 0 - not specified. 1 - Cyrillic captcha. 2 - Latin captcha. + Default: 0. + lang : str, optional + Language code. See the list of supported languages https://2captcha.com/2captcha-api#language. + header_acao : int, optional + 0 - disabled. 1 - enabled. If enabled in.php will include Access-Control-Allow-Origin:* header in + the response. + Default: 0. + softId : int, optional + ID of software developer. Developers who integrated their software with 2Captcha get reward: 10% of + spendings of their software users. + callback : str, optional + URL for pingback (callback) response that will be sent when captcha is solved. URL should be registered on + the server. More info here https://2captcha.com/2captcha-api#pingback. + + Returns + _______ + dict + {'captchaId': 'TASK_ID', 'code': 'COORDINATES'}. The coordinates string in `result['code']` contains + one entry per image from `images`, in the same order, separated by "|". An image that wasn't moved + may be returned by the API as "null". + ''' + + def to_base64(image): + image_method = self.get_method(image) + if 'body' in image_method: + return image_method['body'] + with open(image_method['file'], 'rb') as img: + return b64encode(img.read()).decode('utf-8') + + result = self.solve( + method='drag_drop', + body=to_base64(body), + images=json.dumps([to_base64(image) for image in images]), + **kwargs) + + return result + def solve(self, timeout=0, polling_interval=0, **kwargs): '''Sends captcha, receives result.