diff --git a/LICENSE b/LICENSE index a54843e..d3be45e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Andrei +Copyright (c) 2025 Andrei Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index f53f4c5..e2dee99 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,8 @@ build: python3 -m build upload: - pip3 install twine wheel setuptools build + pip3 install wheel setuptools build + pip3 install twine==6.1.0 twine upload dist/* doc: install diff --git a/docs/conf.py b/docs/conf.py index 017787c..a087a67 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ control, gee_test, turnstile, + amazon_waf, custom_task, fun_captcha, recaptcha_v2, diff --git a/docs/index.rst b/docs/index.rst index e269cf6..8115e54 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,9 +28,12 @@ The library is intended for software developers and is used to work with the `An modules/fun-captcha/example.rst modules/gee-test/example.rst modules/custom-task/example.rst + modules/friend/example.rst + modules/prosopo/example.rst modules/image-to-coordinates/example.rst modules/recaptcha-v2/example.rst modules/recaptcha-v3/example.rst + modules/amazon-waf/example.rst .. toctree:: :maxdepth: 2 diff --git a/docs/modules/amazon-waf/example.rst b/docs/modules/amazon-waf/example.rst new file mode 100644 index 0000000..4eee7c9 --- /dev/null +++ b/docs/modules/amazon-waf/example.rst @@ -0,0 +1,12 @@ +AmazonWAF +========= + +To import this module: + +.. code-block:: python + + from python3_anticaptcha.amazon_waf import AmazonWAF + + +.. autoclass:: python3_anticaptcha.amazon_waf.AmazonWAF + :members: diff --git a/pyproject.toml b/pyproject.toml index b934b03..3657f1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ testpaths = [ addopts = "-vv --tb=short --durations=5" [build-system] -requires = ["setuptools"] +requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -66,10 +66,8 @@ keywords = [ "captcha", "amazon_waf", "friendly-captcha" ] -license = {text = "MIT License"} +license = "MIT" classifiers = [ - "License :: OSI Approved :: MIT License", - "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3", diff --git a/src/python3_anticaptcha/__version__.py b/src/python3_anticaptcha/__version__.py index df4be5e..8a124bf 100644 --- a/src/python3_anticaptcha/__version__.py +++ b/src/python3_anticaptcha/__version__.py @@ -1 +1 @@ -__version__ = "2.1.4" +__version__ = "2.2.0" diff --git a/src/python3_anticaptcha/amazon_waf.py b/src/python3_anticaptcha/amazon_waf.py new file mode 100644 index 0000000..281fd9b --- /dev/null +++ b/src/python3_anticaptcha/amazon_waf.py @@ -0,0 +1,158 @@ +from typing import Union, Optional + +from .core.base import CaptchaParams +from .core.enum import ProxyTypeEnm, CaptchaTypeEnm + +__all__ = ("AmazonWAF",) + + +class AmazonWAF(CaptchaParams): + def __init__( + self, + api_key: str, + captcha_type: Union[CaptchaTypeEnm, str], + websiteURL: str, + websiteKey: str, + iv: str, + context: str, + proxyType: Optional[Union[ProxyTypeEnm, str]] = None, + proxyAddress: Optional[str] = None, + proxyPort: Optional[int] = None, + proxyLogin: Optional[str] = None, + proxyPassword: Optional[str] = None, + userAgent: Optional[str] = None, + sleep_time: Optional[int] = 10, + ): + """ + The class is used to work with FunCaptcha. + + Args: + api_key: Capsolver API key + captcha_type: Captcha type + websiteURL: Address of a target web page. Can be located anywhere on the web site, even in a member area + websiteKey: Value of key from window.gokuProps object in WAF page source code + iv: Value of iv from window.gokuProps object in WAF page source code + context: Value of context from window.gokuProps object in WAF page source code + + proxyType: Type of the proxy + proxyAddress: Proxy IP address IPv4/IPv6. Not allowed to use: + host names instead of IPs, + transparent proxies (where client IP is visible), + proxies from local networks (192.., 10.., 127...) + proxyPort: Proxy port. + proxyLogin: Proxy login. + proxyPassword: Proxy password. + userAgent: Browser UserAgent. + sleep_time: The waiting time between requests to get the result of the Captcha + + Examples: + >>> AmazonWAF(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AmazonTaskProxyless", + ... websiteURL="https://efw47fpad9.execute-api.us-east-1.amazonaws.com/latest", + ... websiteKey="AQIDAgghr5y45ywZwdADFLWk7XOA==", + ... iv="CgAAXFFFFSAAABVk", + ... context="qoJYgnKscdqwdqwdqwaormh/dYYK+Y=", + ... ).captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + >>> await AmazonWAF(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AmazonTaskProxyless", + ... websiteURL="https://efw47fpad9.execute-api.us-east-1.amazonaws.com/latest", + ... websiteKey="AQIDAgghr5y45ywZwdADFLWk7XOA==", + ... iv="CgAAXFFFFSAAABVk", + ... context="qoJYgnKscdqwdqwdqwaormh/dYYK+Y=", + ... ).aio_captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + >>> AmazonWAF(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AmazonTaskProxyless", + ... websiteURL="https://efw47fpad9.execute-api.us-east-1.amazonaws.com/latest", + ... websiteKey="AQIDAgghr5y45ywZwdADFLWk7XOA==", + ... iv="CgAAXFFFFSAAABVk", + ... context="qoJYgnKscdqwdqwdqwaormh/dYYK+Y=", + ... proxyType="http", + ... proxyAddress="0.0.0.0", + ... proxyPort=9988, + ... proxyLogin="proxy_login", + ... proxyPassword="proxy_password", + ... userAgent="some_real_user_agent", + ... ).captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + Notes: + https://anti-captcha.com/apidoc/task-types/AmazonTask + + https://anti-captcha.com/apidoc/task-types/AmazonTaskProxyless + """ + super().__init__(api_key=api_key, sleep_time=sleep_time) + + # validation of the received parameters + if captcha_type == CaptchaTypeEnm.AmazonTask: + self.task_params = dict( + type=captcha_type, + websiteURL=websiteURL, + websitePublicKey=websiteKey, + iv=iv, + context=context, + proxyType=proxyType, + proxyAddress=proxyAddress, + proxyPort=proxyPort, + proxyLogin=proxyLogin, + proxyPassword=proxyPassword, + userAgent=userAgent, + ) + elif captcha_type == CaptchaTypeEnm.AmazonTaskProxyless: + self.task_params = dict( + type=captcha_type, + websiteURL=websiteURL, + websitePublicKey=websiteKey, + iv=iv, + context=context, + ) + else: + raise ValueError( + f"Invalid `captcha_type` parameter set for `{self.__class__.__name__}`, \ + available - {CaptchaTypeEnm.FunCaptchaTaskProxyless.value,CaptchaTypeEnm.FunCaptchaTask.value}" + ) diff --git a/src/python3_anticaptcha/core/enum.py b/src/python3_anticaptcha/core/enum.py index ae9c30e..308bd10 100644 --- a/src/python3_anticaptcha/core/enum.py +++ b/src/python3_anticaptcha/core/enum.py @@ -61,6 +61,9 @@ class CaptchaTypeEnm(str, MyEnum): # FriendlyCaptcha ProsopoTask = "ProsopoTask" ProsopoTaskProxyless = "ProsopoTaskProxyless" + # AmazonWAF + AmazonTask = "AmazonTask" + AmazonTaskProxyless = "AmazonTaskProxyless" # Custom AntiGateTask = "AntiGateTask" diff --git a/tests/test_amazon_waf.py b/tests/test_amazon_waf.py new file mode 100644 index 0000000..671faaf --- /dev/null +++ b/tests/test_amazon_waf.py @@ -0,0 +1,76 @@ +import pytest + +from tests.conftest import BaseTest +from python3_anticaptcha.core.enum import ProxyTypeEnm, CaptchaTypeEnm +from python3_anticaptcha.amazon_waf import AmazonWAF +from python3_anticaptcha.core.serializer import GetTaskResultResponseSer + + +class TestAmazonWAF(BaseTest): + websiteURL = "https://efw47fpad9.execute-api.us-east-1.amazonaws.com/latest" + websiteKey = "AQIDAgghr5y45ywZwdADFLWk7XOA==" + iv = "CgAAXFFFFSAAABVk" + _context = "qoJYgnKscdqwdqwdqwaormh/dYYK+Y=" + + def get_proxy_args(self) -> dict: + proxy_args = super().get_proxy_args() + proxy_args.update({"userAgent": self.get_random_string()}) + return proxy_args + + def test_sio_success(self): + instance = AmazonWAF( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + iv=self.iv, + context=self._context, + captcha_type=CaptchaTypeEnm.AmazonTaskProxyless, + ) + result = instance.captcha_handler() + + assert isinstance(result, dict) + ser_result = GetTaskResultResponseSer(**result) + assert ser_result.errorId == 24 + + async def test_aio_success(self): + instance = AmazonWAF( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + iv=self.iv, + context=self._context, + captcha_type=CaptchaTypeEnm.AmazonTaskProxyless, + ) + result = await instance.aio_captcha_handler() + + assert isinstance(result, dict) + ser_result = GetTaskResultResponseSer(**result) + assert ser_result.errorId == 24 + + @pytest.mark.parametrize("proxyType", ProxyTypeEnm) + def test_proxy_args(self, proxyType: ProxyTypeEnm): + proxy_args = self.get_proxy_args() + proxy_args.update({"proxyType": proxyType}) + + instance = AmazonWAF( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + iv=self.iv, + context=self._context, + captcha_type=CaptchaTypeEnm.AmazonTask, + **proxy_args, + ) + for key, value in proxy_args.items(): + assert instance.task_params[key] == value + + def test_err_captcha_type(self): + with pytest.raises(ValueError): + AmazonWAF( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + iv=self.iv, + context=self._context, + captcha_type=self.get_random_string(length=10), + )