feat: Add extend command to extend ads before expiry (#732)

## ℹ️ Description

Add a manual "extend" command to extend listings shortly before they
expire. This keeps existing watchers/savers and does not count toward
the current 100 ads/month quota.

- Link to the related issue(s): Issue #664
- **Motivation**: Users need a way to extend ads before they expire
without republishing (which consumes quota).

## 📋 Changes Summary

### Implementation
- Add `extend` command case in `run()`
- Implement `extend_ads()` to filter and process eligible ads
- Implement `extend_ad()` for browser automation
- Add German translations for all user-facing messages

### Testing
- Tests cover: filtering logic, date parsing, browser automation, error
handling, edge cases

### Features
- Detects ads within the **8-day extension window** (kleinanzeigen.de
policy)
- Uses API `endDate` from `/m-meine-anzeigen-verwalten.json` for
eligibility
- Only extends active ads (`state == "active"`)
- Handles confirmation dialog (close dialog / skip paid bump-up)
- Updates `updated_on` in YAML after successful extension
- Supports `--ads` parameter to extend specific ad IDs

### Usage
```bash
kleinanzeigen-bot extend                  # Extend all eligible ads
kleinanzeigen-bot extend --ads=1,2,3      # Extend specific ads
```

### ⚙️ Type of Change
- [x]  New feature (adds new functionality without breaking existing
usage)

##  Checklist
- [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 updated documentation where necessary (help text in English
+ German).

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

* **New Features**
* Added an "extend" command to find ads nearing expiry (default 8-day
window) or target specific IDs, open a session, attempt extensions, and
record per-ad outcomes.

* **Documentation**
* Updated CLI/help (bilingual) and README to document the extend
command, options (--ads), default behavior, and expiry-window
limitations.

* **Tests**
* Added comprehensive unit tests for eligibility rules, date parsing
(including German format), edge cases, UI interaction flows, timing, and
error handling.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jens
2026-01-19 10:24:23 +01:00
committed by GitHub
parent a2473081e6
commit 6ef6aea3a8
4 changed files with 734 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ For details on the new smoke test strategy and contributor guidance, see [TESTIN
- **Smart Republishing**: Automatically republish listings at configurable intervals to keep them at the top of search results
- **Bulk Management**: Update or delete multiple listings at once
- **Download Listings**: Download existing listings from your profile to local configuration files
- **Extend Listings**: Extend ads close to expiry to keep watchers/savers and preserve the monthly ad quota
- **Browser Automation**: Uses Chromium-based browsers (Chrome, Edge, Chromium) for reliable automation
- **Flexible Configuration**: Configure defaults once, override per listing as needed
@@ -198,6 +199,7 @@ Commands:
delete - deletes ads
update - updates published ads
download - downloads one or multiple ads
extend - extends active ads that expire soon (keeps watchers/savers and does not count towards the monthly ad quota)
update-check - checks for available updates
update-content-hash recalculates each ad's content_hash based on the current ad_defaults;
use this after changing config.yaml/ad_defaults to avoid every ad being marked "changed" and republished
@@ -221,6 +223,11 @@ Options:
* all: downloads all ads from your profile
* new: downloads ads from your profile that are not locally saved yet
* <id(s)>: provide one or several ads by ID to download, like e.g. "--ads=1,2,3"
--ads=all|<id(s)> (extend) - specifies which ads to extend (DEFAULT: all)
Possible values:
* all: extend all eligible ads in your profile
* <id(s)>: provide one or several ads by ID to extend, like e.g. "--ads=1,2,3"
* Note: kleinanzeigen.de only allows extending ads within 8 days of expiry; ads outside this window are skipped.
--ads=changed|<id(s)> (update) - specifies which ads to update (DEFAULT: changed)
Possible values:
* changed: only update ads that have been modified since last publication