mirror of
https://github.com/morpheus65535/bazarr.git
synced 2026-07-30 17:48:04 -04:00
Added CaptchaAI as an anti-captcha provider
This commit is contained in:
@@ -155,7 +155,7 @@ validators = [
|
||||
Validator('general.days_to_upgrade_subs', must_exist=True, default=7, is_type_of=int, gte=0, lte=30),
|
||||
Validator('general.upgrade_manual', must_exist=True, default=True, is_type_of=bool),
|
||||
Validator('general.anti_captcha_provider', must_exist=True, default=None, is_type_of=(NoneType, str),
|
||||
is_in=[None, 'anti-captcha', 'death-by-captcha']),
|
||||
is_in=[None, 'anti-captcha', 'death-by-captcha', 'captchaai']),
|
||||
Validator('general.wanted_search_frequency', must_exist=True, default=6, is_type_of=int,
|
||||
is_in=[6, 12, 24, 168, ONE_HUNDRED_YEARS_IN_HOURS]),
|
||||
Validator('general.wanted_search_frequency_movie', must_exist=True, default=6, is_type_of=int,
|
||||
@@ -385,6 +385,9 @@ validators = [
|
||||
Validator('deathbycaptcha.username', must_exist=True, default='', is_type_of=str, cast=str),
|
||||
Validator('deathbycaptcha.password', must_exist=True, default='', is_type_of=str, cast=str),
|
||||
|
||||
# captchaai section
|
||||
Validator('captchaai.captchaai_key', must_exist=True, default='', is_type_of=str),
|
||||
|
||||
# napisy24 section
|
||||
Validator('napisy24.username', must_exist=True, default='', is_type_of=str, cast=str),
|
||||
Validator('napisy24.password', must_exist=True, default='', is_type_of=str, cast=str),
|
||||
@@ -753,7 +756,8 @@ def save_settings(settings_items):
|
||||
os.environ["SZ_HI_EXTENSION"] = value or ""
|
||||
|
||||
if key in ['settings-general-anti_captcha_provider', 'settings-anticaptcha-anti_captcha_key',
|
||||
'settings-deathbycaptcha-username', 'settings-deathbycaptcha-password']:
|
||||
'settings-deathbycaptcha-username', 'settings-deathbycaptcha-password',
|
||||
'settings-captchaai-captchaai_key']:
|
||||
configure_captcha = True
|
||||
|
||||
if key in ['update_schedule', 'settings-general-use_sonarr', 'settings-general-use_radarr',
|
||||
@@ -981,6 +985,9 @@ def configure_captcha_func():
|
||||
os.environ["ANTICAPTCHA_CLASS"] = 'DeathByCaptchaProxyLess'
|
||||
os.environ["ANTICAPTCHA_ACCOUNT_KEY"] = str(':'.join(
|
||||
{settings.deathbycaptcha.username, settings.deathbycaptcha.password}))
|
||||
elif settings.general.anti_captcha_provider == 'captchaai' and settings.captchaai.captchaai_key != "":
|
||||
os.environ["ANTICAPTCHA_CLASS"] = 'CaptchaAIProxyLess'
|
||||
os.environ["ANTICAPTCHA_ACCOUNT_KEY"] = str(settings.captchaai.captchaai_key)
|
||||
else:
|
||||
os.environ["ANTICAPTCHA_CLASS"] = ''
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import os
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
import base64
|
||||
import requests
|
||||
from subliminal.cache import region
|
||||
from dogpile.cache.api import NO_VALUE
|
||||
from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask, NoCaptchaTask, ImageToTextTask, \
|
||||
@@ -255,6 +257,206 @@ class DBCPitcher(DBCProxyLessPitcher):
|
||||
return payload
|
||||
|
||||
|
||||
@registry.register
|
||||
class CaptchaAIProxyLessPitcher(Pitcher):
|
||||
name = "CaptchaAIProxyLess"
|
||||
source = "captchaai.com"
|
||||
host = "https://ocr.captchaai.com"
|
||||
timeout = 180
|
||||
|
||||
def __init__(self, website_name, website_url, website_key, tries=3, host=None, *args, **kwargs):
|
||||
super(CaptchaAIProxyLessPitcher, self).__init__(website_name, website_url, website_key, tries=tries, *args,
|
||||
**kwargs)
|
||||
self.host = host or self.host
|
||||
|
||||
def get_client(self):
|
||||
# CaptchaAI exposes a 2Captcha-compatible HTTP API (in.php/res.php), so a
|
||||
# plain requests session is all that is needed.
|
||||
return requests.Session()
|
||||
|
||||
def get_job(self):
|
||||
# The reCAPTCHA task is created in _throw() through the in.php endpoint.
|
||||
pass
|
||||
|
||||
@property
|
||||
def in_params(self):
|
||||
return {
|
||||
"key": self.client_key,
|
||||
"method": "userrecaptcha",
|
||||
"googlekey": self.website_key,
|
||||
"pageurl": self.website_url,
|
||||
"json": 1,
|
||||
}
|
||||
|
||||
def _throw(self):
|
||||
self.client = self.get_client()
|
||||
for i in range(self.tries):
|
||||
try:
|
||||
resp = self.client.post("%s/in.php" % self.host, data=self.in_params, timeout=30)
|
||||
payload = resp.json()
|
||||
if payload.get("status") != 1:
|
||||
request = payload.get("request")
|
||||
if request == "ERROR_ZERO_BALANCE":
|
||||
logger.error("%s: No balance left on captcha solving service. Exiting", self.website_name)
|
||||
return
|
||||
elif request == "ERROR_NO_SLOT_AVAILABLE":
|
||||
logger.info("%s: No captcha solving slot available, retrying", self.website_name)
|
||||
time.sleep(5.0)
|
||||
continue
|
||||
elif request in ("ERROR_KEY_DOES_NOT_EXIST", "ERROR_WRONG_USER_KEY"):
|
||||
logger.error("%s: Bad CaptchaAI API key", self.website_name)
|
||||
return
|
||||
logger.error("%s: CaptchaAI returned an error: %s", self.website_name, request)
|
||||
if i >= self.tries - 1:
|
||||
return
|
||||
continue
|
||||
|
||||
job_id = payload.get("request")
|
||||
deadline = time.time() + self.timeout
|
||||
while time.time() < deadline:
|
||||
time.sleep(5.0)
|
||||
res = self.client.get(
|
||||
"%s/res.php" % self.host,
|
||||
params={"key": self.client_key, "action": "get", "id": job_id, "json": 1},
|
||||
timeout=30,
|
||||
)
|
||||
res_payload = res.json()
|
||||
if res_payload.get("status") == 1:
|
||||
self.success = True
|
||||
return res_payload.get("request")
|
||||
if res_payload.get("request") == "CAPCHA_NOT_READY":
|
||||
continue
|
||||
logger.error("%s: CaptchaAI returned an error: %s", self.website_name,
|
||||
res_payload.get("request"))
|
||||
break
|
||||
logger.info("%s: Captcha solving timed out, retrying", self.website_name)
|
||||
except Exception:
|
||||
if i >= self.tries - 1:
|
||||
logger.error("%s: Captcha solving finally failed. Exiting", self.website_name)
|
||||
return
|
||||
logger.info("%s: Captcha solving failed, retrying", self.website_name)
|
||||
time.sleep(5.0)
|
||||
|
||||
|
||||
@registry.register
|
||||
class CaptchaAIPitcher(CaptchaAIProxyLessPitcher):
|
||||
name = "CaptchaAI"
|
||||
proxy = None
|
||||
needs_proxy = True
|
||||
proxy_type = "HTTP"
|
||||
user_agent = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.proxy = kwargs.pop("proxy", None)
|
||||
self.user_agent = kwargs.pop("user_agent", None)
|
||||
# cookies/is_invisible are accepted for call-site compatibility but unused.
|
||||
kwargs.pop("cookies", None)
|
||||
kwargs.pop("is_invisible", None)
|
||||
super(CaptchaAIPitcher, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def in_params(self):
|
||||
params = super(CaptchaAIPitcher, self).in_params
|
||||
if self.proxy:
|
||||
params.update({"proxy": self.proxy, "proxytype": self.proxy_type})
|
||||
if self.user_agent:
|
||||
params.update({"userAgent": self.user_agent})
|
||||
return params
|
||||
|
||||
|
||||
@registry.register
|
||||
class CaptchaAIImageToTextPitcher(Pitcher):
|
||||
name = "CaptchaAIImageToText"
|
||||
source = "captchaai.com"
|
||||
host = "https://ocr.captchaai.com"
|
||||
timeout = 180
|
||||
image_fp = None
|
||||
|
||||
def __init__(self, website_name, image_fp, tries=3, client_key=None, *args, **kwargs):
|
||||
super().__init__(website_name, tries, client_key, *args, **kwargs)
|
||||
self.tries = tries
|
||||
self.client_key = client_key or os.environ.get("ANTICAPTCHA_ACCOUNT_KEY")
|
||||
if not self.client_key:
|
||||
raise Exception("CaptchaAI key not given, exiting")
|
||||
self.website_name = website_name
|
||||
self.image_fp = image_fp
|
||||
self.success = False
|
||||
self.solve_time = None
|
||||
|
||||
def get_client(self):
|
||||
# CaptchaAI exposes a 2Captcha-compatible HTTP API (in.php/res.php), so a
|
||||
# plain requests session is all that is needed.
|
||||
return requests.Session()
|
||||
|
||||
def get_job(self):
|
||||
# The image task is created in _throw() through the in.php endpoint.
|
||||
pass
|
||||
|
||||
@property
|
||||
def in_params(self):
|
||||
# Read the image bytes and submit them as base64 (method=base64).
|
||||
try:
|
||||
self.image_fp.seek(0)
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
body = base64.b64encode(self.image_fp.read()).decode("ascii")
|
||||
return {
|
||||
"key": self.client_key,
|
||||
"method": "base64",
|
||||
"body": body,
|
||||
"json": 1,
|
||||
}
|
||||
|
||||
def _throw(self):
|
||||
self.client = self.get_client()
|
||||
for i in range(self.tries):
|
||||
try:
|
||||
resp = self.client.post("%s/in.php" % self.host, data=self.in_params, timeout=30)
|
||||
payload = resp.json()
|
||||
if payload.get("status") != 1:
|
||||
request = payload.get("request")
|
||||
if request == "ERROR_ZERO_BALANCE":
|
||||
logger.error("%s: No balance left on captcha solving service. Exiting", self.website_name)
|
||||
return
|
||||
elif request == "ERROR_NO_SLOT_AVAILABLE":
|
||||
logger.info("%s: No captcha solving slot available, retrying", self.website_name)
|
||||
time.sleep(5.0)
|
||||
continue
|
||||
elif request in ("ERROR_KEY_DOES_NOT_EXIST", "ERROR_WRONG_USER_KEY"):
|
||||
logger.error("%s: Bad CaptchaAI API key", self.website_name)
|
||||
return
|
||||
logger.error("%s: CaptchaAI returned an error: %s", self.website_name, request)
|
||||
if i >= self.tries - 1:
|
||||
return
|
||||
continue
|
||||
|
||||
job_id = payload.get("request")
|
||||
deadline = time.time() + self.timeout
|
||||
while time.time() < deadline:
|
||||
time.sleep(5.0)
|
||||
res = self.client.get(
|
||||
"%s/res.php" % self.host,
|
||||
params={"key": self.client_key, "action": "get", "id": job_id, "json": 1},
|
||||
timeout=30,
|
||||
)
|
||||
res_payload = res.json()
|
||||
if res_payload.get("status") == 1:
|
||||
self.success = True
|
||||
return res_payload.get("request")
|
||||
if res_payload.get("request") == "CAPCHA_NOT_READY":
|
||||
continue
|
||||
logger.error("%s: CaptchaAI returned an error: %s", self.website_name,
|
||||
res_payload.get("request"))
|
||||
break
|
||||
logger.info("%s: Captcha solving timed out, retrying", self.website_name)
|
||||
except Exception:
|
||||
if i >= self.tries - 1:
|
||||
logger.error("%s: Captcha solving finally failed. Exiting", self.website_name)
|
||||
return
|
||||
logger.info("%s: Captcha solving failed, retrying", self.website_name)
|
||||
time.sleep(5.0)
|
||||
|
||||
|
||||
@registry.register
|
||||
class AntiCaptchaImageToTextPitcher(Pitcher):
|
||||
name = "AntiCaptchaImageToText"
|
||||
|
||||
@@ -124,9 +124,14 @@ class ZimukuProvider(Provider):
|
||||
return img_fp
|
||||
|
||||
fp = bmp_to_image(image_content)
|
||||
pitcher = pitchers.get_pitcher("AntiCaptchaImageToText" if
|
||||
os.environ["ANTICAPTCHA_CLASS"] == 'AntiCaptchaProxyLess' else
|
||||
"DeathByCaptchaImageToText")("Zimuku", fp)
|
||||
anticaptcha_class = os.environ["ANTICAPTCHA_CLASS"]
|
||||
if anticaptcha_class == 'AntiCaptchaProxyLess':
|
||||
image_pitcher = "AntiCaptchaImageToText"
|
||||
elif anticaptcha_class == 'CaptchaAIProxyLess':
|
||||
image_pitcher = "CaptchaAIImageToText"
|
||||
else:
|
||||
image_pitcher = "DeathByCaptchaImageToText"
|
||||
pitcher = pitchers.get_pitcher(image_pitcher)("Zimuku", fp)
|
||||
return pitcher.throw()
|
||||
|
||||
i = -1
|
||||
|
||||
@@ -62,6 +62,17 @@ const SettingsProvidersView: FunctionComponent = () => {
|
||||
</Anchor>
|
||||
<Message>Link to subscribe</Message>
|
||||
</CollapseBox>
|
||||
<CollapseBox
|
||||
settingKey="settings-general-anti_captcha_provider"
|
||||
on={(value) => value === "captchaai"}
|
||||
>
|
||||
<Text
|
||||
label="Account Key"
|
||||
settingKey="settings-captchaai-captchaai_key"
|
||||
></Text>
|
||||
<Anchor href="https://captchaai.com">CaptchaAI.com</Anchor>
|
||||
<Message>Link to subscribe</Message>
|
||||
</CollapseBox>
|
||||
</Section>
|
||||
<Section header="Integrations">
|
||||
<ProviderView
|
||||
|
||||
@@ -9,4 +9,8 @@ export const antiCaptchaOption: SelectorOption<string>[] = [
|
||||
label: "Death by Captcha",
|
||||
value: "death-by-captcha",
|
||||
},
|
||||
{
|
||||
label: "CaptchaAI",
|
||||
value: "captchaai",
|
||||
},
|
||||
];
|
||||
|
||||
5
frontend/src/types/settings.d.ts
vendored
5
frontend/src/types/settings.d.ts
vendored
@@ -12,6 +12,7 @@ interface Settings {
|
||||
// Anitcaptcha
|
||||
anticaptcha: Settings.Anticaptcha;
|
||||
deathbycaptcha: Settings.DeathByCaptche;
|
||||
captchaai: Settings.CaptchaAI;
|
||||
// Providers
|
||||
opensubtitlescom: Settings.OpenSubtitlesCom;
|
||||
addic7ed: Settings.Addic7ed;
|
||||
@@ -216,6 +217,10 @@ declare namespace Settings {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
interface CaptchaAI {
|
||||
captchaai_key?: string;
|
||||
}
|
||||
|
||||
// Providers
|
||||
|
||||
interface BaseProvider {
|
||||
|
||||
Reference in New Issue
Block a user