Skip to content

Commit

Permalink
Add support for Deathbycaptcha.com service (#9)
Browse files Browse the repository at this point in the history
* Add deathbycaptcha.com service
  • Loading branch information
sergey-scat authored Oct 9, 2022
1 parent e09946e commit 9bf6485
Show file tree
Hide file tree
Showing 6 changed files with 619 additions and 10 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Unicaps is a unified Python API for CAPTCHA solving services.
- Uses native service protocol/endpoints (eg, no needs in patching _hosts_ file)
- Has both synchronous and asynchronous client
- Supports 11 types of CAPTCHAs
- Supports 6 CAPTCHA solving services
- Supports 7 CAPTCHA solving services
- Written Pythonic way and is intended for humans

## Installation
Expand Down Expand Up @@ -72,6 +72,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com) ||||||||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||||||||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||||||||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||||||||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) ||||||||||||

### Image CAPTCHA
Expand All @@ -82,6 +83,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||||| Latin ||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||||| Latin ||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||||| Cyrillic/Latin ||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||||| Latin ||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||||| Cyrillic/Latin ||

### Text CAPTCHA
Expand All @@ -99,6 +101,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) ||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | English, Russian |

### reCAPTCHA v2
Expand All @@ -109,6 +112,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) ||||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) ||||||||

<sup>1</sup> Support of solving reCAPTCHA on Google services (e.g. Google Search) </br>
Expand All @@ -124,6 +128,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) ||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) ||||||

### FunCaptcha (Arkose Labs)
Expand All @@ -134,6 +139,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) ||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) ||||||

### KeyCAPTCHA
Expand All @@ -144,6 +150,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||

### Geetest
Expand All @@ -154,6 +161,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||||

### Geetest v4
Expand All @@ -164,6 +172,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||

### hCaptcha
Expand All @@ -174,6 +183,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||||

### Capy Puzzle
Expand All @@ -184,6 +194,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) ||||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) ||||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) ||||||
| [deathbycaptcha.com](http://deathbycaptcha.com) ||||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) ||||||

### TikTok CAPTCHA
Expand All @@ -194,6 +205,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||

## Supported Proxy types
Expand All @@ -204,6 +216,7 @@ if __name__ == '__main__':
| [azcaptcha.com](https://azcaptcha.com/) |||||
| [captcha.guru](https://captcha.guru/ru/reg/?ref=127872) |||||
| [cptch.net](https://cptch.net/auth/signup?frm=0ebc1ab34eb04f67ac320f020a8f709f) |||||
| [deathbycaptcha.com](http://deathbycaptcha.com) |||||
| [rucaptcha.com](https://rucaptcha.com?from=9863637) |||||

## How to...
Expand Down
3 changes: 2 additions & 1 deletion acceptance_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
'anti-captcha.com': 'API_KEY_ANTICAPTCHA',
'azcaptcha.com': 'API_KEY_AZCAPTCHA',
'captcha.guru': 'API_KEY_CAPTCHA_GURU',
'cptch.net': 'API_KEY_CPTCH_NET'
'cptch.net': 'API_KEY_CPTCH_NET',
'deathbycaptcha.com': 'API_KEY_DEATHBYCAPTCHA'
}

EXAMPLES = [
Expand Down
180 changes: 174 additions & 6 deletions tests/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
"""

import base64
import json
import os.path
import pathlib
from random import choice
from unittest import mock

from unicaps import CaptchaSolvingService, exceptions as exc
from unicaps.captcha import (
from unicaps import CaptchaSolvingService, exceptions as exc # type: ignore
from unicaps.captcha import ( # type: ignore
CaptchaType, ImageCaptcha, RecaptchaV2, RecaptchaV3, FunCaptcha, TextCaptcha,
KeyCaptcha, GeeTest, GeeTestV4, HCaptcha, CapyPuzzle, TikTokCaptcha
)
from unicaps.common import CaptchaAlphabet, CaptchaCharType, WorkerLanguage
from unicaps.proxy import ProxyServer
from unicaps.common import CaptchaAlphabet, CaptchaCharType, WorkerLanguage # type: ignore
from unicaps.proxy import ProxyServer # type: ignore

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))

Expand Down Expand Up @@ -108,6 +109,12 @@
method='POST',
url='https://cptch.net/in.php',
data=dict(key=API_KEY, json=1, soft_id="164")
),
CaptchaSolvingService.DEATHBYCAPTCHA: dict(
method='POST',
headers={'Accept': 'application/json'},
url='http://api.dbcapi.me/api/captcha',
data=dict(authtoken=API_KEY)
)
}

Expand Down Expand Up @@ -389,16 +396,134 @@
41: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
42: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
2: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
3: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
4: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
5: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
6: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
7: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
8: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
9: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
10: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
11: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
12: {'data': dict(type=4,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
13: {'data': dict(type=4,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
14: {'data': dict(type=4, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'data-s': 'test3'}))},
15: {'data': dict(type=5,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
16: {'data': dict(type=5, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'action': 'test3'}))},
17: {'data': dict(type=5, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'min_score': 0.9}))},
18: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
19: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
20: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
21: None,
22: None,
23: None,
24: None,
25: None,
26: None,
27: {'data': dict(type=7,
hcaptcha_params=json.dumps({'sitekey': 'test1', 'pageurl': 'test2'}))},
28: None,
29: None,
30: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
31: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
32: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
33: {'data': dict(
type=4,
token_params=json.dumps(
{'googlekey': 'test1', 'pageurl': 'test2', 'data-s': 'test3'}
)
)},
34: {'data': dict(
type=5,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
35: None,
36: None,
37: None,
38: None,
39: None,
40: {'data': dict(
type=4,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
41: {'data': dict(
type=5,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
42: {'data': dict(
type=6,
funcaptcha_params=json.dumps(
dict(publickey='test1', pageurl='test2')
)
)},
}
}
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC[CaptchaSolvingService.TWOCAPTCHA]
)


def get_http_resp_obj(ret_value):
def get_http_resp_obj(ret_value, status_code=200, reason_phrase='OK', is_success=True,
is_error=False):
""" Mocked response object """
obj = mock.Mock()
obj.json = ret_value.copy
obj.status_code = status_code
obj.reason_phrase = reason_phrase
obj.is_success = is_success
obj.is_error = is_error
return obj


Expand Down Expand Up @@ -468,6 +593,19 @@ def get_http_resp_obj(ret_value):
9: None,
10: None,
11: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
2: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
3: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
4: None,
5: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
6: None,
7: None,
8: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
9: None,
10: None,
11: None,
}
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
Expand Down Expand Up @@ -526,13 +664,25 @@ def get_http_resp_obj(ret_value):
9: None,
10: None,
11: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: None,
5: dict(task_id='1234567890', extra={}),
6: None,
7: None,
8: dict(task_id='1234567890', extra={}),
9: None,
10: None,
11: None,
}
}
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.TWOCAPTCHA]
)


OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC = {
1: exc.ServiceError,
2: exc.AccessDeniedError,
Expand Down Expand Up @@ -604,6 +754,24 @@ def get_http_resp_obj(ret_value):
'ERROR_PAGEURL ERROR_GOOGLEKEY ERROR'.split()))),
8: get_http_resp_obj(dict(status=0, request='ERROR_CAPTCHA_UNSOLVABLE')),
9: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: get_http_resp_obj(dict(status=255)),
2: get_http_resp_obj(dict(status=255, error=choice(
['token authentication disabled', 'not-logged-in', 'banned']))),
3: get_http_resp_obj(dict(status=255, error='insufficient-funds')),
4: get_http_resp_obj(dict(status=255, error='service-overload')),
5: None,
6: get_http_resp_obj(dict(status=255, error=choice(
['upload-failed', 'invalid-captcha']))),
7: get_http_resp_obj(dict(status=255, error=choice(
['ERROR_PAGEURL', 'Invalid base64-encoded CAPTCHA', 'Not a (CAPTCHA) image',
'Empty CAPTCHA image', 'ERROR_GOOGLEKEY', 'ERROR_PAGEURL',
'ERROR_PUBLICKEY', 'ERROR_SITEKEY', 'ERROR_ACTION', 'ERROR_MIN_SCORE',
'ERROR_MIN_SCORE_NOT_FLOAT']))),
8: None,
9: get_http_resp_obj(dict(status=255, error=choice(
['ERROR_PROXYTYPE', 'ERROR_PROXY']))),
}
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[CaptchaSolvingService.RUCAPTCHA] = (
Expand Down
3 changes: 2 additions & 1 deletion tests/test_service_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
'azcaptcha': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.HCAPTCHA, CaptchaType.FUNCAPTCHA),
'cptch_net': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3),
# 'deathbycaptcha': (CaptchaType.RECAPTCHAV2,)
'deathbycaptcha': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.HCAPTCHA, CaptchaType.FUNCAPTCHA)
}
BASE_REQUESTS = ('GetBalance', 'GetStatus', 'ReportGood', 'ReportBad')
TASK_REQUEST_PREPARE_PARAMS = ('self', 'captcha', 'proxy', 'user_agent', 'cookies')
Expand Down
Loading

0 comments on commit 9bf6485

Please sign in to comment.