diff --git a/README.md b/README.md index cd76d51..87f1818 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,14 @@ publishing: delete_old_ads: "AFTER_PUBLISH" # one of: AFTER_PUBLISH, BEFORE_PUBLISH, NEVER delete_old_ads_by_title: true # only works if delete_old_ads is set to BEFORE_PUBLISH +# captcha-Handling (optional) +# To ensure that the bot does not require manual confirmation after a captcha, but instead automatically pauses for a defined period and then restarts, you can enable the captcha section: + +captcha: + auto_restart: true # If true, the bot aborts when a Captcha appears and retries publishing later + # If false (default), the Captcha must be solved manually to continue + restart_delay: 1h 30m # Time to wait before retrying after a Captcha was encountered (default: 6h) + # browser configuration browser: # https://peter.sh/experiments/chromium-command-line-switches/ diff --git a/src/kleinanzeigen_bot/__init__.py b/src/kleinanzeigen_bot/__init__.py index 8499d6a..77c9ef6 100644 --- a/src/kleinanzeigen_bot/__init__.py +++ b/src/kleinanzeigen_bot/__init__.py @@ -20,6 +20,7 @@ from . import extract, resources from ._version import __version__ from .ads import calculate_content_hash, get_description_affixes from .utils import dicts, error_handlers, loggers, misc +from .utils.exceptions import CaptchaEncountered from .utils.files import abspath from .utils.i18n import Locale, get_current_locale, pluralize, set_current_locale from .utils.misc import ainput, ensure, is_frozen, parse_datetime, parse_decimal @@ -773,7 +774,16 @@ class KleinanzeigenBot(WebScrapingMixin): # wait for captcha ############################# try: - await self.web_find(By.CSS_SELECTOR, "iframe[name^='a-'][src^='https://www.google.com/recaptcha/api2/anchor?']", timeout = 2) + await self.web_find( + By.CSS_SELECTOR, + "iframe[name^='a-'][src^='https://www.google.com/recaptcha/api2/anchor?']", + timeout = 2) + + if self.config.get("captcha", {}).get("auto_restart", False): + LOG.warning("Captcha recognized - auto-restart enabled, abort run...") + raise CaptchaEncountered(misc.parse_duration(self.config.get("captcha", {}).get("restart_delay", "6h"))) + + # Fallback: manuell LOG.warning("############################################") LOG.warning("# Captcha present! Please solve the captcha.") LOG.warning("############################################") @@ -1128,7 +1138,7 @@ class KleinanzeigenBot(WebScrapingMixin): else dicts.safe_get(ad_cfg, "description", "prefix") if dicts.safe_get(ad_cfg, "description", "prefix") is not None # 3. Global prefix from config - else get_description_affixes(self.config, prefix=True) + else get_description_affixes(self.config, prefix = True) or "" # Default to empty string if all sources are None ) @@ -1140,7 +1150,7 @@ class KleinanzeigenBot(WebScrapingMixin): else dicts.safe_get(ad_cfg, "description", "suffix") if dicts.safe_get(ad_cfg, "description", "suffix") is not None # 3. Global suffix from config - else get_description_affixes(self.config, prefix=False) + else get_description_affixes(self.config, prefix = False) or "" # Default to empty string if all sources are None ) diff --git a/src/kleinanzeigen_bot/__main__.py b/src/kleinanzeigen_bot/__main__.py index 68d54fe..e970750 100644 --- a/src/kleinanzeigen_bot/__main__.py +++ b/src/kleinanzeigen_bot/__main__.py @@ -3,7 +3,23 @@ SPDX-FileCopyrightText: © Sebastian Thomschke and contributors SPDX-License-Identifier: AGPL-3.0-or-later SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/ """ -import sys -import kleinanzeigen_bot +import sys, time +from gettext import gettext as _ -kleinanzeigen_bot.main(sys.argv) +import kleinanzeigen_bot +from kleinanzeigen_bot.utils.exceptions import CaptchaEncountered +from kleinanzeigen_bot.utils.misc import format_timedelta + +# --------------------------------------------------------------------------- # +# Main loop: run bot → if captcha → sleep → restart +# --------------------------------------------------------------------------- # +while True: + try: + kleinanzeigen_bot.main(sys.argv) # runs & returns when finished + sys.exit(0) # not using `break` to prevent process closing issues + + except CaptchaEncountered as ex: + delay = ex.restart_delay + print(_("[INFO] Captcha detected. Sleeping %s before restart...") % format_timedelta(delay)) + time.sleep(delay.total_seconds()) + # loop continues and starts a fresh run diff --git a/src/kleinanzeigen_bot/resources/translations.de.yaml b/src/kleinanzeigen_bot/resources/translations.de.yaml index 099abc5..6e06a2c 100644 --- a/src/kleinanzeigen_bot/resources/translations.de.yaml +++ b/src/kleinanzeigen_bot/resources/translations.de.yaml @@ -12,6 +12,11 @@ getopt.py: short_has_arg: "option -%s not recognized": "Option -%s unbekannt" +################################################# +kleinanzeigen_bot/__main__.py: +################################################# + module: + "[INFO] Captcha detected. Sleeping %s before restart...": "[INFO] Captcha erkannt. Warte %s h bis zum Neustart..." ################################################# kleinanzeigen_bot/__init__.py: @@ -78,6 +83,7 @@ kleinanzeigen_bot/__init__.py: " -> SUCCESS: ad published with ID %s": " -> ERFOLG: Anzeige mit ID %s veröffentlicht" " -> effective ad meta:": " -> effektive Anzeigen-Metadaten:" "Could not set city from location": "Stadt konnte nicht aus dem Standort gesetzt werden" + "Captcha recognized - auto-restart enabled, abort run...": "Captcha erkannt - Auto-Neustart aktiviert, Durchlauf wird beendet..." __set_condition: "Unable to close condition dialog!": "Kann den Dialog für Artikelzustand nicht schließen!" diff --git a/src/kleinanzeigen_bot/utils/exceptions.py b/src/kleinanzeigen_bot/utils/exceptions.py new file mode 100644 index 0000000..1fb09de --- /dev/null +++ b/src/kleinanzeigen_bot/utils/exceptions.py @@ -0,0 +1,18 @@ +""" +SPDX-FileCopyrightText: © Sebastian Thomschke and contributors +SPDX-License-Identifier: AGPL-3.0-or-later +SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/ +""" +from datetime import timedelta + + +class KleinanzeigenBotError(RuntimeError): + """Base class for all custom bot-related exceptions.""" + + +class CaptchaEncountered(KleinanzeigenBotError): + """Raised when a Captcha was detected and auto-restart is enabled.""" + + def __init__(self, restart_delay: timedelta): + super().__init__() + self.restart_delay = restart_delay diff --git a/src/kleinanzeigen_bot/utils/loggers.py b/src/kleinanzeigen_bot/utils/loggers.py index dcc1bf9..6ac4a48 100644 --- a/src/kleinanzeigen_bot/utils/loggers.py +++ b/src/kleinanzeigen_bot/utils/loggers.py @@ -28,6 +28,9 @@ LOG_ROOT:Final[logging.Logger] = logging.getLogger() def configure_console_logging() -> None: + # if a StreamHandler already exists, do not append it again + if any(isinstance(h, logging.StreamHandler) for h in LOG_ROOT.handlers): + return class CustomFormatter(logging.Formatter): LEVEL_COLORS = {