mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-07-09 12:41:05 +02:00
## ℹ️ Description - Link to the related issue(s): Issue #1157 - Kleinanzeigen no longer offers individual/custom shipping. This updates publishing, validation, docs, schemas, translations, and tests so existing `shipping_costs` configs are handled gracefully instead of failing against removed DOM controls. ## 📋 Changes Summary - Keep `shipping_costs` for legacy configs/downloaded ads, but mark it deprecated in schema/docs and ignore it during publishing. - Remove publishing interaction with removed individual-shipping DOM controls. - Require predefined non-empty `shipping_options` for `sell_directly`. - Update docs, generated schemas/config artifacts, German translations, and unit tests. - Verified live DOM for `sell_directly: true` + `shipping_type: SHIPPING` + predefined `shipping_options` via local ignored script; no ad submission performed. ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated shipping guides to deprecate custom individual shipping costs and make publishing depend on per-ad predefined `shipping_options` (DHL/Hermes; selectable options must come from a single size group). * Added migration guidance replacing `shipping_costs` with the correct `shipping_options`. * Clarified “Sell directly” requires `shipping_type: SHIPPING`, non-empty `shipping_options`, and `FIXED`/`NEGOTIABLE` pricing. * **Bug Fixes** * Publishing is blocked when `shipping_costs` is provided without `shipping_options`. * Removed unsupported individual-shipping handling and tightened “Sell directly” eligibility rules. * **Tests** * Expanded unit coverage for deprecation behavior, content handling, and direct-buy validation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
434 lines
19 KiB
Markdown
434 lines
19 KiB
Markdown
# Ad Configuration Reference
|
||
|
||
Complete reference for ad YAML files in kleinanzeigen-bot.
|
||
|
||
## File Format
|
||
|
||
Each ad is described in a separate JSON or YAML file. The bot loads whichever files match the `ad_files` glob in `config.yaml`. Many users keep the default `ad_` prefix for downloaded files (for example, `ad_laptop.yaml`), but the filename itself is user-controlled.
|
||
Examples below use YAML, but JSON uses the same keys and structure.
|
||
|
||
Parameter values specified in the `ad_defaults` section of `config.yaml` don't need to be specified again in the ad configuration file.
|
||
|
||
## Quick Start
|
||
|
||
Generate sample ad files using the download command:
|
||
|
||
```bash
|
||
# Download all ads from your profile
|
||
kleinanzeigen-bot download --ads=all
|
||
|
||
# Download only new ads (not locally saved yet)
|
||
kleinanzeigen-bot download --ads=new
|
||
|
||
# Download specific ads by ID
|
||
kleinanzeigen-bot download --ads=1,2,3
|
||
```
|
||
|
||
For full JSON schema with IDE autocompletion support, see:
|
||
|
||
- [schemas/ad.schema.json](https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/main/schemas/ad.schema.json)
|
||
|
||
Looking for shipping setup? See [Shipping Configuration](#shipping-configuration), including the [Shipping Options Reference](#shipping-options-reference).
|
||
|
||
📖 **[Complete Main Configuration Reference →](CONFIGURATION.md)**
|
||
|
||
Full documentation for `config.yaml` including all options, timeouts, browser settings, update checks, and ad_defaults.
|
||
|
||
## Configuration Structure
|
||
|
||
### Basic Ad Properties
|
||
|
||
Description values can be multiline. See <https://yaml-multiline.info/> for YAML syntax examples.
|
||
|
||
```yaml
|
||
# 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
|
||
|
||
You can add prefix and suffix text to your ad descriptions in two ways:
|
||
|
||
#### New Format (Recommended)
|
||
|
||
In your `config.yaml` file you can specify a `description_prefix` and `description_suffix` under the `ad_defaults` section:
|
||
|
||
```yaml
|
||
ad_defaults:
|
||
description_prefix: "Prefix text"
|
||
description_suffix: "Suffix text"
|
||
```
|
||
|
||
#### Legacy Format
|
||
|
||
In your ad configuration file you can specify a `description_prefix` and `description_suffix`:
|
||
|
||
```yaml
|
||
description_prefix: "Prefix text"
|
||
description_suffix: "Suffix text"
|
||
```
|
||
|
||
#### Precedence
|
||
|
||
The ad-level setting has precedence over the `config.yaml` default. If you specify both, the ad-level setting will be used. We recommend using the `config.yaml` defaults as it is more flexible and easier to manage.
|
||
|
||
### Category
|
||
|
||
Built-in category name, custom category name from `config.yaml`, or category ID.
|
||
|
||
```yaml
|
||
# Built-in category name (see default list at
|
||
# https://github.com/Second-Hand-Friends/kleinanzeigen-bot/blob/main/src/kleinanzeigen_bot/resources/categories.yaml)
|
||
category: "Elektronik > Notebooks"
|
||
|
||
# Custom category name (defined in config.yaml)
|
||
category: "Verschenken & Tauschen > Tauschen"
|
||
|
||
# Category ID
|
||
category: 161/278
|
||
```
|
||
|
||
### Price and Price Type
|
||
|
||
```yaml
|
||
price: # Price in euros; decimals allowed but will be rounded to nearest whole euro on processing
|
||
# (prefer whole euros for predictability)
|
||
price_type: # one of: FIXED, NEGOTIABLE, GIVE_AWAY (default: NEGOTIABLE)
|
||
```
|
||
|
||
### Automatic Price Reduction
|
||
|
||
When `auto_price_reduction.enabled` is set to `true`, the bot evaluates whether a price reduction is due each time the ad is republished via the `publish` command — the price is only lowered when all eligibility gates (repost cycle, day delay, minimum floor) are satisfied.
|
||
|
||
**Important:** By default, price reductions only apply when using the `publish` command (which deletes the old ad and creates a new one). Using the `update` command does NOT advance the reduction cycle. Set `on_update: true` (see below) to also apply reductions during update runs. Regardless of the `on_update` setting, the effective reduced price is always restored before submitting an update — this prevents previously applied reductions from being silently lost.
|
||
|
||
`repost_count` is tracked for every ad (and persisted inside the corresponding `ad_*.yaml`) so reductions continue across runs.
|
||
|
||
`min_price` is required whenever `enabled` is `true` and must be less than or equal to `price`; this makes an explicit floor (including `0`) mandatory. If `min_price` equals the current price, the bot will log a warning and perform no reduction.
|
||
|
||
**Note:** `repost_count` and price reduction counters are only incremented and persisted after a successful publish. Failed publish attempts do not advance the counters.
|
||
|
||
When automatic price reduction is enabled, each `publish` (and optionally `update`) run logs one clear INFO message per ad summarizing the outcome—whether the price was reduced, kept, or the reduction was delayed (and why). The `verify` command also previews these outcomes for all configured ads so you can validate your pricing configuration without triggering a publish cycle. Ads without `auto_price_reduction` configured are silently skipped at default log level.
|
||
|
||
If you run with `-v` / `--verbose`, the bot additionally logs structured decision details (repost counts, cycle state, day delay, reference timestamps) and the full cycle-by-cycle calculation trace (base price, reduction value, rounded step result, and floor clamp).
|
||
|
||
```yaml
|
||
auto_price_reduction:
|
||
enabled: # true or false to enable automatic price reduction on reposts (default: false)
|
||
strategy: # "PERCENTAGE" or "FIXED" (required when enabled is true)
|
||
amount: # Reduction amount; interpreted as percent for PERCENTAGE or currency units for FIXED
|
||
# (prefer whole euros for predictability)
|
||
min_price: # Required when enabled is true; minimum price floor
|
||
# (use 0 for no lower bound, prefer whole euros for predictability)
|
||
delay_reposts: # Number of reposts to wait before first reduction (default: 0)
|
||
delay_days: # Number of days to wait after publication before reductions (default: 0)
|
||
on_update: # Also apply price reductions during the update command (default: false)
|
||
```
|
||
|
||
**Note:** All prices are rounded to whole euros after each reduction step.
|
||
|
||
#### PERCENTAGE Strategy Example
|
||
|
||
```yaml
|
||
price: 150
|
||
price_type: FIXED
|
||
auto_price_reduction:
|
||
enabled: true
|
||
strategy: PERCENTAGE
|
||
amount: 10
|
||
min_price: 90
|
||
delay_reposts: 0
|
||
delay_days: 0
|
||
```
|
||
|
||
This posts the ad at 150 € the first time, then 135 € (−10%), 122 € (−10%), 110 € (−10%), 99 € (−10%), and stops decreasing at 90 €.
|
||
|
||
**Note:** The bot applies commercial rounding (ROUND_HALF_UP) to full euros after each reduction step. For example, 121.5 rounds to 122, and 109.8 rounds to 110. This step-wise rounding affects the final price progression, especially for percentage-based reductions.
|
||
|
||
#### FIXED Strategy Example
|
||
|
||
```yaml
|
||
price: 150
|
||
price_type: FIXED
|
||
auto_price_reduction:
|
||
enabled: true
|
||
strategy: FIXED
|
||
amount: 15
|
||
min_price: 90
|
||
delay_reposts: 0
|
||
delay_days: 0
|
||
```
|
||
|
||
This posts the ad at 150 € the first time, then 135 € (−15 €), 120 € (−15 €), 105 € (−15 €), and stops decreasing at 90 €.
|
||
|
||
#### Note on `delay_days` Behavior
|
||
|
||
The `delay_days` parameter counts complete 24-hour periods (whole days) since the ad was published. For example, if `delay_days: 7` and the ad was published 6 days and 23 hours ago, the reduction will not yet apply. This ensures predictable behavior and avoids partial-day ambiguity.
|
||
|
||
Combined timeline example: with `republication_interval: 3`, `delay_reposts: 1`, and `delay_days: 2`, the first reduction is typically applied on the third publish cycle (around day 8 in a steady schedule, because due ads are republished after more than 3 full days):
|
||
|
||
- day 0: first publish, no reduction
|
||
- day 4: second publish, still waiting for repost delay
|
||
- day 8: third publish, first reduction can apply
|
||
|
||
#### Update-Mode Price Reductions (`on_update`)
|
||
|
||
By default (`on_update: false`), price reductions only apply during `publish` (full republish). Set `on_update: true` to also apply reductions when running the `update` command (in-place ad modification).
|
||
|
||
**Update-mode semantics differ from publish-mode in two ways:**
|
||
|
||
- `delay_days` applies normally — the bot still checks that enough days have elapsed since the last publication before reducing.
|
||
- `delay_reposts` is **ignored** — update-mode reductions always evaluate based on the current `price_reduction_count` regardless of `repost_count`, because `update` does not increment `repost_count`.
|
||
|
||
This means an ad with `delay_reposts: 2` that has only been published once can still receive a price reduction via `update` if `on_update: true` and `delay_days` has elapsed.
|
||
|
||
#### Restore-First Behavior
|
||
|
||
When `on_update` is enabled, the reduced effective price is preserved across mixed `publish`/`update` runs:
|
||
|
||
1. The ad file keeps the configured base `price`.
|
||
2. The bot persists `price_reduction_count` after successful runs and recalculates the effective reduced price from `price` + `price_reduction_count` on each run.
|
||
3. If you manually edit the `price` field in the ad file, the next run uses the new value as the base for recalculation.
|
||
|
||
This ensures price reductions accumulate correctly regardless of whether you use `publish`, `update`, or both.
|
||
|
||
#### Verify Command Preview
|
||
|
||
The `verify` command previews pricing outcomes for both modes:
|
||
|
||
- **Publish preview**: Always shown when `auto_price_reduction.enabled` is `true`. Shows the effective price after applying reductions based on the current `repost_count`, `price_reduction_count`, and delay settings.
|
||
- **Update preview**: Always shown when `auto_price_reduction.enabled` is `true`. Reports the update-mode outcome: if `on_update` is `true`, it shows the reduction result; if `on_update` is `false` (default), it reports that update-mode reductions are disabled and shows the restored effective price (no new cycle).
|
||
|
||
```yaml
|
||
# Example: enable reductions for both publish and update
|
||
auto_price_reduction:
|
||
enabled: true
|
||
strategy: PERCENTAGE
|
||
amount: 10
|
||
min_price: 90
|
||
delay_reposts: 0
|
||
delay_days: 7
|
||
on_update: true
|
||
```
|
||
|
||
With this configuration, the price is reduced on every `publish` and also on `update` runs, as long as at least 7 days have passed since the last publication.
|
||
|
||
Set `auto_price_reduction.enabled: false` (or omit the entire `auto_price_reduction` section) to keep the existing behavior—prices stay fixed and `repost_count` only acts as tracked metadata for future changes.
|
||
|
||
You can configure `auto_price_reduction` once under `ad_defaults` in `config.yaml`. The `min_price` and `on_update` can be set there or overridden per ad file as needed.
|
||
|
||
### Special Attributes
|
||
|
||
Special attributes are category-specific key/value pairs. Use the download command to inspect existing ads in your category and reuse the keys you see under `special_attributes`.
|
||
|
||
For condition values, use the API values from downloaded ads instead of translated display labels.
|
||
Common API values are `new`, `like_new`, `ok`, `alright`, and `defect` (for example API `like_new` corresponds to the German UI label `Sehr Gut`), but available values can vary by category.
|
||
|
||
```yaml
|
||
special_attributes:
|
||
# condition_s: like_new # common values include new, like_new, ok, alright, defect
|
||
|
||
# Example for rental properties
|
||
# haus_mieten.zimmer_d: "3" # Number of rooms
|
||
```
|
||
|
||
### Shipping Configuration
|
||
|
||
> **Important:** Individual/custom shipping (`shipping_costs`) is no longer available on
|
||
> kleinanzeigen.de. Only predefined DHL/Hermes carrier options are supported.
|
||
|
||
```yaml
|
||
shipping_type: # one of: PICKUP, SHIPPING, NOT_APPLICABLE (default: SHIPPING)
|
||
|
||
# DEPRECATED: Individual/custom shipping is no longer supported.
|
||
# If shipping_costs is set without shipping_options, publishing is blocked
|
||
# with an error. Remove shipping_costs and use predefined shipping_options
|
||
# instead (see below). shipping_costs in downloaded ads is still readable
|
||
# for backward compatibility.
|
||
shipping_costs: # e.g. 2.95 — BLOCKS publishing if set without shipping_options; migrate!
|
||
|
||
# Specify predefined shipping options / packages.
|
||
# Only DHL and Hermes carrier options are valid. You can select multiple
|
||
# packages, but only from one size group (S, M, L)!
|
||
# See the "Shipping Options Reference" table below for all available options.
|
||
shipping_options: [] # non-empty list required for direct-buy (sell_directly)
|
||
|
||
# Example (size S only):
|
||
# shipping_options:
|
||
# - DHL_2
|
||
# - Hermes_Päckchen
|
||
|
||
sell_directly: # true or false, requires shipping_type SHIPPING, non-empty predefined shipping_options, and price_type FIXED or NEGOTIABLE (default: false)
|
||
```
|
||
|
||
**Migration from `shipping_costs` to `shipping_options`:**
|
||
|
||
If your ad YAML currently uses `shipping_costs` (e.g. `shipping_costs: 4.50`) and
|
||
`shipping_type: SHIPPING` with no `shipping_options`, replace it with the appropriate
|
||
predefined option from the table below. For example, replace:
|
||
|
||
```yaml
|
||
shipping_type: SHIPPING
|
||
shipping_costs: 4.50
|
||
shipping_options: []
|
||
```
|
||
|
||
with:
|
||
|
||
```yaml
|
||
shipping_type: SHIPPING
|
||
shipping_options:
|
||
- DHL_2
|
||
```
|
||
|
||
Choose the option that matches your typical package size. If you use multiple carriers,
|
||
select multiple options from the same size group.
|
||
|
||
**`shipping_costs` in downloaded ads:** Extraction still reads `shipping_costs` from
|
||
live ads for backward compatibility. However, attempting to publish an ad that has
|
||
`shipping_costs` configured without `shipping_options` will **fail with an error** —
|
||
you must migrate to predefined `shipping_options` before publishing.
|
||
|
||
|
||
#### Shipping Options Reference
|
||
|
||
You can select multiple options, but **only from one size group** (S, M, or L). Each option corresponds to a specific carrier package. Prices are set by the carrier and may change over time.
|
||
|
||
| Config Name | Size Group | Carrier | Package | Max Dimensions / Weight |
|
||
| --- | --- | --- | --- | --- |
|
||
| **Size S (Klein)** | | | | |
|
||
| `DHL_2` | S | DHL | DHL Paket 2 kg | 2 kg, max 60 × 30 × 15 cm |
|
||
| `Hermes_Päckchen` | S | Hermes | Hermes Päckchen | L+shortest side ≤ 37 cm, max 25 kg |
|
||
| `Hermes_S` | S | Hermes | Hermes S-Paket | L+W+H ≤ 50 cm, max 25 kg |
|
||
| **Size M (Mittel)** | | | | |
|
||
| `DHL_5` | M | DHL | DHL Paket 5 kg | 5 kg |
|
||
| `Hermes_M` | M | Hermes | Hermes M-Paket | Standard medium package |
|
||
| **Size L (Groß)** | | | | |
|
||
| `DHL_10` | L | DHL | DHL Paket 10 kg | 10 kg |
|
||
| `DHL_20` | L | DHL | DHL Paket 20 kg | 20 kg |
|
||
| `DHL_31,5` | L | DHL | DHL Paket 31,5 kg | 31.5 kg |
|
||
| `Hermes_L` | L | Hermes | Hermes L-Paket | Standard large package |
|
||
|
||
**Shipping types:**
|
||
|
||
- `PICKUP` - Buyer picks up the item
|
||
- `SHIPPING` - Item is shipped. Non-empty `shipping_options` are required for predefined carrier selection and direct-buy (`sell_directly`). Legacy `shipping_costs` is accepted for backward compatibility but ignored during publishing.
|
||
- `NOT_APPLICABLE` - Shipping not applicable for this item
|
||
|
||
**Sell Directly:**
|
||
When `sell_directly: true`, buyers can purchase the item directly through the platform without contacting the seller first. This feature requires `shipping_type: SHIPPING` **and** a non-empty list of predefined `shipping_options`. Individual `shipping_costs` alone does **not** qualify for direct-buy. A fixed or negotiable price is also required.
|
||
|
||
### Images
|
||
|
||
List of wildcard patterns to select images. If relative paths are specified, they are relative to this ad configuration file.
|
||
|
||
```yaml
|
||
images:
|
||
# - laptop_*.{jpg,png}
|
||
```
|
||
|
||
### Contact Information
|
||
|
||
Contact details for the ad. These override defaults from `config.yaml`.
|
||
|
||
```yaml
|
||
contact:
|
||
name:
|
||
street:
|
||
zipcode:
|
||
phone: "" # IMPORTANT: surround phone number with quotes to prevent removal of leading zeros
|
||
```
|
||
|
||
### Republication Interval
|
||
|
||
How often the ad should be republished (in days). Overrides `ad_defaults.republication_interval` from `config.yaml`.
|
||
|
||
```yaml
|
||
republication_interval: # every X days the ad should be re-published (default: 7)
|
||
```
|
||
|
||
### Auto-Managed Fields
|
||
|
||
The following fields are automatically managed by the bot. Do not manually edit these unless you know what you're doing.
|
||
|
||
```yaml
|
||
id: # The ID assigned by kleinanzeigen.de
|
||
created_on: # ISO timestamp when the ad was first published
|
||
updated_on: # ISO timestamp when the ad was last published
|
||
content_hash: # Hash of the ad content, used to detect changes
|
||
repost_count: # How often the ad has been (re)published; used for automatic price reductions
|
||
price_reduction_count: # Auto-managed reduction cycle counter (starts at 0); used with price to recompute effective reduced price
|
||
```
|
||
|
||
`price_reduction_count` is maintained by the bot after successful runs and should not be edited manually.
|
||
|
||
## Complete Example
|
||
|
||
```yaml
|
||
# yaml-language-server: $schema=https://raw.githubusercontent.com/Second-Hand-Friends/kleinanzeigen-bot/refs/heads/main/schemas/ad.schema.json
|
||
active: true
|
||
type: OFFER
|
||
title: "Example Ad Title"
|
||
description: |
|
||
This is a multi-line description.
|
||
You can add as much detail as you want here.
|
||
The bot will preserve line breaks and formatting.
|
||
|
||
description_prefix: "For sale: " # Optional ad-level override; defaults can live in config.yaml
|
||
description_suffix: " Please message if interested!" # Optional ad-level override
|
||
|
||
category: "Elektronik > Notebooks"
|
||
|
||
price: 150
|
||
price_type: FIXED
|
||
|
||
auto_price_reduction:
|
||
enabled: true
|
||
strategy: PERCENTAGE
|
||
amount: 10
|
||
min_price: 90
|
||
delay_reposts: 0
|
||
delay_days: 0
|
||
on_update: false
|
||
|
||
shipping_type: SHIPPING
|
||
shipping_options:
|
||
- DHL_2
|
||
- Hermes_Päckchen
|
||
sell_directly: true
|
||
|
||
images:
|
||
- "images/laptop_*.jpg"
|
||
|
||
contact:
|
||
name: "John Doe"
|
||
street: "Main Street 123"
|
||
zipcode: "12345"
|
||
phone: "0123456789"
|
||
|
||
republication_interval: 7
|
||
```
|
||
|
||
## Best Practices
|
||
|
||
1. **Use meaningful filenames**: Name your ad files descriptively, e.g., `ad_laptop_hp_15.yaml`
|
||
1. **Set defaults in config.yaml**: Put common values in `ad_defaults` to avoid repetition
|
||
1. **Test before bulk publishing**: Use `--ads=changed` or `--ads=new` to test changes before republishing all ads
|
||
1. **Back up your ad files**: Keep them in version control if you want to track changes
|
||
1. **Use price reductions carefully**: Set appropriate `min_price` to avoid underpricing
|
||
1. **Check shipping options**: Ensure your shipping options match the actual package size and cost
|
||
|
||
## Troubleshooting
|
||
|
||
- **Schema validation errors**: Run `kleinanzeigen-bot verify` (binary) or `pdm run app verify` (source) to see which fields fail validation.
|
||
- **Price reduction not applying**: Confirm `auto_price_reduction.enabled` is `true`, `min_price` is set, and you are using `publish` (not `update`, unless `on_update: true`). Run `kleinanzeigen-bot verify` to preview outcomes, or add `-v` for detailed decision data including repost/day-delay state. Remember ad-level values override `ad_defaults`.
|
||
- **Shipping configuration issues**: Use `shipping_type: SHIPPING` with non-empty `shipping_options` for predefined DHL/Hermes package shipping and direct-buy. Individual `shipping_costs` is deprecated — publishing an ad with `shipping_costs` and no `shipping_options` will fail with an error. Remove `shipping_costs` from your config and migrate to `shipping_options` (pick options from a single size group S/M/L).
|
||
- **Category not found**: Verify the category name or ID and check any custom mappings in `config.yaml`.
|
||
- **File naming/prefix mismatch**: Ensure ad files match your `ad_files` glob and prefix (default `ad_`).
|
||
- **Image path resolution**: Relative paths are resolved from the ad file location; use absolute paths and check file permissions if images are not found.
|