diff --git a/README.md b/README.md index 7283902..2c6552c 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ ad_defaults: price_type: NEGOTIABLE # one of: FIXED, NEGOTIABLE, GIVE_AWAY, NOT_APPLICABLE shipping_type: SHIPPING # one of: PICKUP, SHIPPING, NOT_APPLICABLE shipping_costs: # e.g. 2.95 + sell_directly: false # requires shipping_options to take effect contact: name: "" street: "" @@ -295,6 +296,22 @@ special_attributes: shipping_type: # one of: PICKUP, SHIPPING, NOT_APPLICABLE shipping_costs: # e.g. 2.95 +# specify shipping options / packages +# it is possible to select multiple packages, but only from one size (S, M, L)! +# possible package types for size S: +# - DHL_2 +# - Hermes_Päckchen +# - Hermes_S +# possible package types for size M: +# - DHL_5 +# - Hermes_M +# possible package types for size L: +# - DHL_10 +# - DHL_31,5 +# - Hermes_L +shipping_options: [] +sell_directly: # true or false, requires shipping_options to take effect + # list of wildcard patterns to select images # if relative paths are specified, then they are relative to this ad configuration file images: diff --git a/kleinanzeigen_bot/__init__.py b/kleinanzeigen_bot/__init__.py index 2414197..0cc7129 100644 --- a/kleinanzeigen_bot/__init__.py +++ b/kleinanzeigen_bot/__init__.py @@ -469,13 +469,15 @@ class KleinanzeigenBot(SeleniumMixin): self.__set_category(ad_file, ad_cfg) ############################# - # set shipping type/costs + # set shipping type/options/costs ############################# if ad_cfg["shipping_type"] == "PICKUP": try: self.web_click(By.XPATH, '//*[contains(@class, "ShippingPickupSelector")]//label[text()[contains(.,"Nur Abholung")]]/input[@type="radio"]') except NoSuchElementException as ex: LOG.debug(ex, exc_info = True) + elif ad_cfg["shipping_options"]: + self.__set_shipping_options(ad_cfg) elif ad_cfg["shipping_costs"]: try: self.web_click(By.XPATH, '//*[contains(@class, "ShippingOption")]//input[@type="radio"]') @@ -495,6 +497,16 @@ class KleinanzeigenBot(SeleniumMixin): if safe_get(ad_cfg, "price"): self.web_input(By.XPATH, "//input[@id='post-ad-frontend-price' or @id='micro-frontend-price' or @id='pstad-price']", ad_cfg["price"]) + ############################# + # set sell_directly + ############################# + sell_directly = ad_cfg["sell_directly"] + if sell_directly and ad_cfg["shipping_type"] == "SHIPPING" and ad_cfg["shipping_options"] and price_type in {"FIXED", "NEGOTIABLE"}: + try: + self.web_click(By.XPATH, '//*[contains(@class, "BuyNowSection")]//span[contains(@class, "Toggle--Slider")]') + except NoSuchElementException as ex: + LOG.debug(ex, exc_info = True) + ############################# # set description ############################# @@ -609,6 +621,43 @@ class KleinanzeigenBot(SeleniumMixin): raise NoSuchElementException(f"Failed to set special attribute [{special_attribute_key}]") from ex LOG.debug("Successfully set attribute field [%s] to [%s]...", special_attribute_key, special_attribute_value) + def __set_shipping_options(self, ad_cfg: dict[str, Any]) -> None: + try: + shipping_option_mapping = { + "DHL_2": ("Klein", "Paket 2 kg"), + "Hermes_Päckchen": ("Klein", "Päckchen"), + "Hermes_S": ("Klein", "S-Paket"), + "DHL_5": ("Mittel", "Paket 5 kg"), + "Hermes_M": ("Mittel", "M-Paket"), + "DHL_10": ("Mittel", "Paket 10 kg"), + "DHL_31,5": ("Groß", "Paket 31,5 kg"), + "Hermes_L": ("Groß", "L-Paket"), + } + try: + mapped_shipping_options = [shipping_option_mapping[option] for option in ad_cfg["shipping_options"]] + shipping_sizes, shipping_packages = zip(*mapped_shipping_options) + except KeyError as ex: + raise KeyError(f"Unknown shipping option(s), please refer to the documentation/README: {ad_cfg['shipping_options']}") from ex + + unique_shipping_sizes = set(shipping_sizes) + if len(unique_shipping_sizes) > 1: + raise ValueError("You can only specify shipping options for one package size!") + + shipping_size, = unique_shipping_sizes + self.web_click(By.XPATH, f'//*[contains(@class, "ShippingOption")]//input[@type="radio" and @data-testid="{shipping_size}"]') + + for shipping_package in shipping_packages: + self.web_click( + By.XPATH, + '//*[contains(@class, "CarrierOptionsPopup")]' + '//*[contains(@class, "CarrierOption")]' + f'//input[@type="checkbox" and @data-testid="{shipping_package}"]' + ) + + self.web_click(By.XPATH, '//*[contains(@class, "ReactModalPortal")]//button[.//*[text()[contains(.,"Weiter")]]]') + except NoSuchElementException as ex: + LOG.debug(ex, exc_info = True) + def __upload_images(self, ad_cfg: dict[str, Any]): LOG.info(" -> found %s", pluralize("image", ad_cfg["images"])) image_upload = self.web_find(By.XPATH, "//input[@type='file']") diff --git a/kleinanzeigen_bot/resources/ad_fields.yaml b/kleinanzeigen_bot/resources/ad_fields.yaml index 8df9d2f..7a29244 100644 --- a/kleinanzeigen_bot/resources/ad_fields.yaml +++ b/kleinanzeigen_bot/resources/ad_fields.yaml @@ -8,6 +8,8 @@ price: price_type: # one of: FIXED, NEGOTIABLE, GIVE_AWAY, NOT_APPLICABLE shipping_type: # one of: PICKUP, SHIPPING, NOT_APPLICABLE shipping_costs: +shipping_options: [] # see README.md for more information +sell_directly: # requires shipping_options to take effect images: [] contact: name: diff --git a/kleinanzeigen_bot/resources/config_defaults.yaml b/kleinanzeigen_bot/resources/config_defaults.yaml index f1ee3fd..60e7fd8 100644 --- a/kleinanzeigen_bot/resources/config_defaults.yaml +++ b/kleinanzeigen_bot/resources/config_defaults.yaml @@ -10,6 +10,7 @@ ad_defaults: suffix: "" price_type: NEGOTIABLE # one of: FIXED, NEGOTIABLE, GIVE_AWAY, NOT_APPLICABLE shipping_type: SHIPPING # one of: PICKUP, SHIPPING, NOT_APPLICABLE + sell_directly: false # requires shipping_options to take effect contact: name: "" street: ""