diff --git a/README.md b/README.md index a1ec80c..8b7c107 100644 --- a/README.md +++ b/README.md @@ -287,30 +287,58 @@ The `--config` and `--logfile` flags override only their specific paths. They do - **Linux:** System-wide follows XDG Base Directory spec; portable stays in the current working directory. - **macOS:** System-wide uses `~/Library/Application Support/kleinanzeigen-bot` (and related dirs); portable stays in the current directory. -### 1) Main configuration +### 1) Main configuration ⚙️ -When executing the app it by default looks for a `config.yaml` file in the current directory. If it does not exist it will be created automatically. +The main configuration file (`config.yaml`) is **required** to run the bot. It contains your login credentials and controls all bot behavior. -The configuration file to be used can also be specified using the `--config ` command line parameter. It must point to a YAML or JSON file. -Valid file extensions are `.json`, `.yaml` and `.yml` - -The configuration file supports many options including login credentials, ad file patterns, browser settings, timeouts, and update check configuration. To generate a default configuration file with all current defaults, run: +**Quick start:** ```bash +# Generate a config file with all defaults kleinanzeigen-bot create-config + +# Or specify a custom location +kleinanzeigen-bot --config /path/to/config.yaml publish ``` -For the complete configuration reference with all available options and detailed explanations, see [Configuration Reference](docs/CONFIGURATION.md). +**Minimal config.yaml:** -### 2) Ad configuration +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json +login: + username: "your_username" + password: "your_password" +``` -Each ad is described in a separate JSON or YAML file with prefix `ad_`. The prefix is configurable in config file. +📖 **[Complete Configuration Reference →](docs/CONFIGURATION.md)** -Parameter values specified in the `ad_defaults` section of the `config.yaml` file don't need to be specified again in the ad configuration file. +Full documentation including timeout tuning, browser settings, ad defaults, diagnostics, and all available options. -For the complete ad configuration reference including automatic price reduction, shipping options, and description prefix/suffix, see [Ad Configuration Reference](docs/AD_CONFIGURATION.md). +### 2) Ad configuration 📝 -### 3) Using an existing browser window +Each ad is defined in a separate YAML/JSON file (default pattern: `ad_*.yaml`). These files specify the title, description, price, category, images, and other ad-specific settings. + +**Quick example (`ad_laptop.yaml`):** + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json +active: true +title: "Gaming Laptop - RTX 3060" +description: | + Powerful gaming laptop in excellent condition. + Includes original box and charger. +category: "Elektronik > Notebooks" +price: 450 +price_type: NEGOTIABLE +images: + - "laptop/*.jpg" # Relative to ad file location (or use absolute paths); glob patterns supported +``` + +📖 **[Complete Ad Configuration Reference →](docs/AD_CONFIGURATION.md)** + +Full documentation including automatic price reduction, shipping options, category IDs, and special attributes. + +### 3) Using an existing browser window (Optional) By default a new browser process will be launched. To reuse a manually launched browser window/process, you can enable remote debugging. This is useful for debugging or when you want to keep your browser session open. diff --git a/docs/AD_CONFIGURATION.md b/docs/AD_CONFIGURATION.md index c344d7d..b2e0532 100644 --- a/docs/AD_CONFIGURATION.md +++ b/docs/AD_CONFIGURATION.md @@ -26,7 +26,11 @@ kleinanzeigen-bot download --ads=1,2,3 For full JSON schema with IDE autocompletion support, see: -- [schemas/ad.schema.json](../schemas/ad.schema.json) +- [schemas/ad.schema.json](https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json) + +📖 **[Complete Main Configuration Reference →](CONFIGURATION.md)** + +Full documentation for `config.yaml` including all options, timeouts, browser settings, update checks, and ad_defaults. ## Configuration Structure @@ -35,11 +39,13 @@ For full JSON schema with IDE autocompletion support, see: Description values can be multiline. See for YAML syntax examples. ```yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/refs/heads/main/schemas/ad.schema.json -active: # true or false (default: true) -type: # one of: OFFER, WANTED (default: OFFER) -title: # Ad title -description: # Ad description +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json +active: true +type: OFFER +title: "Your Ad Title" +description: | + Your ad description here. + Supports multiple lines. ``` ### Description Prefix and Suffix diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a6c53f4..7188c01 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -12,20 +12,51 @@ kleinanzeigen-bot create-config For full JSON schema with IDE autocompletion support, see: -- [schemas/config.schema.json](../schemas/config.schema.json) +- [schemas/config.schema.json](https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json) To enable IDE autocompletion in `config.yaml`, add this at the top of the file: ```yaml -# yaml-language-server: $schema=schemas/config.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json ``` For ad files, use the ad schema instead: ```yaml -# yaml-language-server: $schema=schemas/ad.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json ``` +## Minimal Configuration Example + +Here's the smallest viable `config.yaml` to get started. Only the **login** section is required—everything else uses sensible defaults: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json + +# REQUIRED: Your kleinanzeigen.de credentials +login: + username: "your_username" + password: "your_password" + +# OPTIONAL: Where to find your ad files (default pattern shown) +# ad_files: +# - "./**/ad_*.{json,yml,yaml}" + +# OPTIONAL: Default values for all ads +# ad_defaults: +# price_type: NEGOTIABLE +# shipping_type: SHIPPING +# republication_interval: 7 +``` + +Run `kleinanzeigen-bot create-config` to generate a complete configuration with all available options and their default values. + +The `ad_files` setting controls where the bot looks for your ad YAML files (default pattern: `./**/ad_*.{json,yml,yaml}`). The `ad_defaults` section lets you set default values that apply to all ads—things like price type, shipping options, and republication interval. + +📖 **[Complete Ad Configuration Reference →](AD_CONFIGURATION.md)** + +Full documentation for ad YAML files including automatic price reduction, description prefix/suffix, shipping options, category IDs, and special attributes. + ## File Location The bot looks for `config.yaml` in the current directory by default. You can specify a different location using the `--config` command line option: @@ -71,7 +102,7 @@ ad_defaults: republication_interval: 7 # every X days ads should be re-published ``` -> **Tip:** For current defaults of all timeout and diagnostic settings, run `kleinanzeigen-bot create-config` or see the [JSON schema](../schemas/config.schema.json). +> **Tip:** For current defaults of all timeout and diagnostic settings, run `kleinanzeigen-bot create-config` or see the [JSON schema](https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json). ### categories @@ -309,4 +340,4 @@ kleinanzeigen-bot create-config This generates a config file with `exclude_none=True`, giving you all the non-None defaults. -For the complete machine-readable reference, see the [JSON schema](../schemas/config.schema.json). +For the complete machine-readable reference, see the [JSON schema](https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/config.schema.json). diff --git a/src/kleinanzeigen_bot/__init__.py b/src/kleinanzeigen_bot/__init__.py index 618aeb2..2283a62 100644 --- a/src/kleinanzeigen_bot/__init__.py +++ b/src/kleinanzeigen_bot/__init__.py @@ -252,8 +252,7 @@ class KleinanzeigenBot(WebScrapingMixin): # noqa: PLR0904 # Log installation mode and config location (INFO level for user visibility) mode_display = "portable (current directory)" if self.installation_mode == "portable" else "system-wide (XDG directories)" - LOG.info("Installation mode: %s", mode_display) - LOG.info("Config file: %s", self.config_file_path) + LOG.info("Installation mode: %s [%s]", mode_display, self.config_file_path) async def run(self, args:list[str]) -> None: self.parse_args(args) @@ -585,7 +584,7 @@ class KleinanzeigenBot(WebScrapingMixin): # noqa: PLR0904 header=( "# yaml-language-server: $schema=" "https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot" - "/refs/heads/main/schemas/config.schema.json" + "/main/schemas/config.schema.json" ), exclude = {"ad_defaults": {"description"}}, ) @@ -599,12 +598,20 @@ class KleinanzeigenBot(WebScrapingMixin): # noqa: PLR0904 self.config = Config.model_validate(config_yaml, strict = True, context = self.config_file_path) # load built-in category mappings - self.categories = dicts.load_dict_from_module(resources, "categories.yaml", "categories") - deprecated_categories = dicts.load_dict_from_module(resources, "categories_old.yaml", "categories") + self.categories = dicts.load_dict_from_module(resources, "categories.yaml", "") + LOG.debug("Loaded %s categories from categories.yaml", len(self.categories)) + deprecated_categories = dicts.load_dict_from_module(resources, "categories_old.yaml", "") + LOG.debug("Loaded %s categories from categories_old.yaml", len(deprecated_categories)) self.categories.update(deprecated_categories) + custom_count = 0 if self.config.categories: + custom_count = len(self.config.categories) self.categories.update(self.config.categories) - LOG.info(" -> found %s", pluralize("category", self.categories)) + LOG.debug("Loaded %s categories from config.yaml (custom)", custom_count) + total_count = len(self.categories) + if total_count == 0: + LOG.warning("No categories loaded - category files may be missing or empty") + LOG.debug("Loaded %s categories in total", total_count) # populate browser_config object used by WebScrapingMixin self.browser_config.arguments = self.config.browser.arguments diff --git a/src/kleinanzeigen_bot/resources/translations.de.yaml b/src/kleinanzeigen_bot/resources/translations.de.yaml index c284224..a7aa538 100644 --- a/src/kleinanzeigen_bot/resources/translations.de.yaml +++ b/src/kleinanzeigen_bot/resources/translations.de.yaml @@ -63,8 +63,11 @@ kleinanzeigen_bot/__init__.py: load_config: "config": "Konfiguration" - " -> found %s": "-> %s gefunden" - "category": "Kategorie" + "Loaded %s categories from categories.yaml": "%s Kategorien aus categories.yaml geladen" + "Loaded %s categories from categories_old.yaml": "%s Kategorien aus categories_old.yaml geladen" + "Loaded %s categories from config.yaml (custom)": "%s Kategorien aus config.yaml geladen (benutzerdefiniert)" + "Loaded %s categories in total": "%s Kategorien insgesamt geladen" + "No categories loaded - category files may be missing or empty": "Keine Kategorien geladen - Kategorie-Dateien fehlen oder sind leer" check_and_wait_for_captcha: "# Captcha present! Please solve the captcha.": "# Captcha vorhanden! Bitte lösen Sie das Captcha." @@ -138,9 +141,8 @@ kleinanzeigen_bot/__init__.py: "Found extend button on page %s": "'Verlängern'-Button auf Seite %s gefunden" finalize_installation_mode: - "Config file: %s": "Konfigurationsdatei: %s" "First run detected, prompting user for installation mode": "Erster Start erkannt, frage Benutzer nach Installationsmodus" - "Installation mode: %s": "Installationsmodus: %s" + "Installation mode: %s [%s]": "Installationsmodus: %s [%s]" publish_ads: "Processing %s/%s: '%s' from [%s]...": "Verarbeite %s/%s: '%s' von [%s]..." @@ -686,7 +688,7 @@ kleinanzeigen_bot/utils/xdg_paths.py: "Failed to create %s %s: %s": "Fehler beim Erstellen von %s %s: %s" detect_installation_mode: "Detected installation mode: %s": "Erkannter Installationsmodus: %s" - "No existing installation found": "Keine bestehende Installation gefunden" + "No existing configuration (portable or system-wide) found": "Keine bestehende Konfiguration (portabel oder systemweit) gefunden" prompt_installation_mode: "Non-interactive mode detected, defaulting to portable installation": "Nicht-interaktiver Modus erkannt, Standard-Installation: portabel" "Choose installation type:": "Installationstyp wählen:" diff --git a/src/kleinanzeigen_bot/utils/dicts.py b/src/kleinanzeigen_bot/utils/dicts.py index e6c895d..06e6bbe 100644 --- a/src/kleinanzeigen_bot/utils/dicts.py +++ b/src/kleinanzeigen_bot/utils/dicts.py @@ -79,7 +79,7 @@ def load_dict(filepath:str, content_label:str = "") -> dict[str, Any]: def load_dict_if_exists(filepath:str, content_label:str = "") -> dict[str, Any] | None: abs_filepath = files.abspath(filepath) - LOG.info("Loading %s[%s]...", content_label and content_label + " from " or "", abs_filepath) + LOG.debug("Loading %s[%s]...", content_label and content_label + " from " or "", abs_filepath) __, file_ext = os.path.splitext(filepath) if file_ext not in {".json", ".yaml", ".yml"}: diff --git a/src/kleinanzeigen_bot/utils/xdg_paths.py b/src/kleinanzeigen_bot/utils/xdg_paths.py index 2fa6d9f..6f29a7f 100644 --- a/src/kleinanzeigen_bot/utils/xdg_paths.py +++ b/src/kleinanzeigen_bot/utils/xdg_paths.py @@ -89,7 +89,7 @@ def detect_installation_mode() -> InstallationMode | None: LOG.debug("Checking for portable config at: %s", portable_config) if portable_config.exists(): - LOG.info("Detected installation mode: %s", "portable") + LOG.debug("Detected installation mode: %s", "portable") return "portable" # Check for XDG installation @@ -97,11 +97,11 @@ def detect_installation_mode() -> InstallationMode | None: LOG.debug("Checking for XDG config at: %s", xdg_config) if xdg_config.exists(): - LOG.info("Detected installation mode: %s", "xdg") + LOG.debug("Detected installation mode: %s", "xdg") return "xdg" # Neither exists - first run - LOG.info("No existing installation found") + LOG.info("No existing configuration (portable or system-wide) found") return None