Skip to content

Commit

Permalink
Merge pull request #204 from AndreiDrang/master
Browse files Browse the repository at this point in the history
6.1.2
  • Loading branch information
AndreiDrang committed Jan 3, 2024
2 parents 7a176ab + c3d2413 commit 1681968
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
lemin_captcha,
rotate_captcha,
datadome_captcha,
friendly_captcha,
cyber_siara_captcha,
draw_around_captcha,
bounding_box_captcha,
Expand Down
3 changes: 3 additions & 0 deletions docs/modules/enum/info.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ To import this module:

.. autoenum:: python_rucaptcha.core.enums.GridCaptchaEnm
:members:

.. autoenum:: python_rucaptcha.core.enums.FriendlyCaptchaEnm
:members:
12 changes: 12 additions & 0 deletions docs/modules/friendly-captcha/example.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FriendlyCaptcha
===============

To import this module:

.. code-block:: python
from python_rucaptcha.friendly_captcha import FriendlyCaptcha
.. autoclass:: python_rucaptcha.friendly_captcha.FriendlyCaptcha
:members:
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ sphinx==7.2.6
pallets_sphinx_themes==2.1.1
myst-parser==2.0.0
autodoc_pydantic==2.0.1
pydantic==2.5.2
pydantic==2.5.3
pydantic-settings==2.1.0
enum-tools[sphinx]==0.11.0
2 changes: 1 addition & 1 deletion requirements.style.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# codestyle
isort==5.*
black==23.10.0
black==23.12.0
autoflake==2.*
2 changes: 1 addition & 1 deletion src/python_rucaptcha/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "6.1"
__version__ = "6.1.2"
5 changes: 5 additions & 0 deletions src/python_rucaptcha/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ class CoordinatesCaptchaEnm(str, MyEnum):

class GridCaptchaEnm(str, MyEnum):
GridTask = "GridTask"


class FriendlyCaptchaEnm(str, MyEnum):
FriendlyCaptchaTaskProxyless = "FriendlyCaptchaTaskProxyless"
FriendlyCaptchaTask = "FriendlyCaptchaTask"
124 changes: 124 additions & 0 deletions src/python_rucaptcha/friendly_captcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from typing import Union

from .core.base import BaseCaptcha
from .core.enums import FriendlyCaptchaEnm


class FriendlyCaptcha(BaseCaptcha):
def __init__(
self,
websiteURL: str,
websiteKey: str,
method: Union[str, FriendlyCaptchaEnm] = FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless,
*args,
**kwargs,
):
"""
The class is used to work with Friendly Captcha.
Args:
rucaptcha_key: User API key
websiteURL: The full URL of target web page where the captcha is loaded. We do not open the page,
not a problem if it is available only for authenticated users
websiteKey: The value of `data-sitekey` attribute of captcha's `div` element on page.
method: Captcha type
Examples:
>>> FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
... websiteKey="2FZFEVS1FZCGQ9",
... websiteURL="https://example.com",
... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value
... ).captcha_handler()
{
"errorId":0,
"status":"ready",
"solution":{
"token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v"
},
"cost":"0.00299",
"ip":"1.2.3.4",
"createTime":1692863536,
"endTime":1692863556,
"solveCount":1,
"taskId":75190409731
}
>>> FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
... websiteKey="2FZFEVS1FZCGQ9",
... websiteURL="https://example.com",
... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value
... ).captcha_handler()
{
"errorId":0,
"status":"ready",
"solution":{
"token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v"
},
"cost":"0.00299",
"ip":"1.2.3.4",
"createTime":1692863536,
"endTime":1692863556,
"solveCount":1,
"taskId":75190409731
}
>>> await FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
... websiteKey="2FZFEVS1FZCGQ9",
... websiteURL="https://example.com",
... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value
... ).aio_captcha_handler()
{
"errorId":0,
"status":"ready",
"solution":{
"token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v"
},
"cost":"0.00299",
"ip":"1.2.3.4",
"createTime":1692863536,
"endTime":1692863556,
"solveCount":1,
"taskId":75190409731
}
Returns:
Dict with full server response
Notes:
https://rucaptcha.com/api-docs/friendly-captcha
"""
super().__init__(method=method, *args, **kwargs)

self.create_task_payload["task"].update({"websiteURL": websiteURL, "websiteKey": websiteKey})

# check user params
if method not in FriendlyCaptchaEnm.list_values():
raise ValueError(f"Invalid method parameter set, available - {FriendlyCaptchaEnm.list_values()}")

def captcha_handler(self, **kwargs) -> dict:
"""
Sync solving method
Args:
kwargs: additional params for `requests` library
Returns:
Dict with full server response
Notes:
Check class docstirng for more info
"""

return self._processing_response(**kwargs)

async def aio_captcha_handler(self) -> dict:
"""
Async solving method
Returns:
Dict with full server response
Notes:
Check class docstirng for more info
"""
return await self._aio_processing_response()
1 change: 1 addition & 0 deletions src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def run(self):
turnstile
amazon
amazon_waf
friendly-captcha
""",
python_requires=REQUIRES_PYTHON,
zip_safe=False,
Expand Down
131 changes: 131 additions & 0 deletions tests/test_friendly_captcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import pytest

from tests.conftest import BaseTest
from python_rucaptcha.core.enums import FriendlyCaptchaEnm
from python_rucaptcha.core.serializer import GetTaskResultResponseSer
from python_rucaptcha.friendly_captcha import FriendlyCaptcha


class TestFriendlyCaptcha(BaseTest):
websiteURL = "https://example.cc/foo/bar.html"
websiteKey = "SAb83IIB"

kwargs_params = {
"proxyType": "socks5",
"proxyAddress": BaseTest.proxyAddress,
"proxyPort": BaseTest.proxyPort,
}

def test_methods_exists(self):
assert "captcha_handler" in FriendlyCaptcha.__dict__.keys()
assert "aio_captcha_handler" in FriendlyCaptcha.__dict__.keys()

@pytest.mark.parametrize("method", FriendlyCaptchaEnm.list_values())
def test_args(self, method: str):
instance = FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=method,
)
assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY
assert instance.create_task_payload["task"]["type"] == method
assert instance.create_task_payload["task"]["websiteURL"] == self.websiteURL
assert instance.create_task_payload["task"]["websiteKey"] == self.websiteKey

def test_kwargs(self):
instance = FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless,
**self.kwargs_params,
)
assert set(self.kwargs_params.keys()).issubset(set(instance.create_task_payload["task"].keys()))
assert set(self.kwargs_params.values()).issubset(set(instance.create_task_payload["task"].values()))

"""
Success tests
"""

def test_basic_data(self):
instance = FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value,
)

result = instance.captcha_handler()

assert isinstance(result, dict) is True
if not result["errorId"]:
assert result["status"] in ("ready", "processing")
assert isinstance(result["taskId"], int) is True
else:
assert result["errorId"] in (1, 12)
assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE"

assert result.keys() == GetTaskResultResponseSer().to_dict().keys()

async def test_aio_basic_data(self):
instance = FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value,
)

result = await instance.aio_captcha_handler()

assert isinstance(result, dict) is True
if not result["errorId"]:
assert result["status"] in ("ready", "processing")
assert isinstance(result["taskId"], int) is True
else:
assert result["errorId"] in (1, 12)
assert result["errorCode"] in ("ERROR_CAPTCHA_UNSOLVABLE", FriendlyCaptcha.NO_CAPTCHA_ERR)

assert result.keys() == GetTaskResultResponseSer().to_dict().keys()

def test_context_basic_data(self):
with FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value,
) as instance:
assert instance

async def test_context_aio_basic_data(self):
async with FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value,
) as instance:
assert instance

"""
Fail tests
"""

def test_wrong_method(self):
with pytest.raises(ValueError):
FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
websiteKey=self.websiteKey,
method=self.get_random_string(length=5),
)

def test_no_websiteURL(self):
with pytest.raises(TypeError):
FriendlyCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, websiteKey=self.websiteKey)

def test_no_websiteKey(self):
with pytest.raises(TypeError):
FriendlyCaptcha(
rucaptcha_key=self.RUCAPTCHA_KEY,
websiteURL=self.websiteURL,
)

0 comments on commit 1681968

Please sign in to comment.