from __future__ import annotations import json import re from string import Formatter from typing import Any, Callable try: from . import item_axis_policy except ImportError: # Allows local smoke tests with top-level imports. import item_axis_policy HARDCORE_POSITION_FAMILY_CHOICES = [ "any", "penetrative", "foreplay", "interaction", "manual", "oral", "outercourse", "anal", "climax", "threesome", "group", ] HARDCORE_POSITION_FOCUS_CHOICES = [ "keep_pool", "penetration_only", "foreplay_only", "interaction_only", "manual_only", "oral_only", "outercourse_only", "anal_only", "climax_only", "threesome_only", "group_only", ] HARDCORE_POSITION_KEY_CHOICES = [ "missionary", "cowgirl", "reverse_cowgirl", "doggy", "bent_over", "face_down_ass_up", "standing", "side_lying", "edge_supported", "kneeling", "lotus_lap", "face_sitting", "sixty_nine", "reclining_oral", "straddled_oral", "spread_leg_oral", "chair_oral", "kissing", "caressing", "breast_touch", "face_touch", "undressing", "body_worship", "nipple_play", "ass_grab", "thigh_kissing", "hair_holding", "wrist_pinning", "dirty_talk", "position_transition", "guided_positioning", "camera_showing", "watching", "aftercare", "cleanup", "fingering", "clit_rubbing", "mutual_masturbation", "boobjob", "testicle_sucking", "penis_licking", "handjob", "footjob", "open_thighs", "front_back", ] HARDCORE_POSITION_FAMILY_SUBCATEGORIES = { "any": [ "penetrative_sex", "foreplay_teasing", "body_worship_touching", "clothing_position_transitions", "dominant_guidance", "camera_performance", "manual_stimulation", "oral_sex", "outercourse_sex", "anal_double_penetration", "threesomes", "group_coordination", "group_sex_orgy", "cumshot_climax", "aftercare_cleanup", ], "penetrative": ["penetrative_sex"], "foreplay": ["foreplay_teasing"], "interaction": [ "foreplay_teasing", "body_worship_touching", "clothing_position_transitions", "dominant_guidance", "camera_performance", "group_coordination", "aftercare_cleanup", ], "manual": ["manual_stimulation"], "oral": ["oral_sex"], "outercourse": ["outercourse_sex", "manual_stimulation"], "anal": ["anal_double_penetration"], "climax": ["cumshot_climax"], "threesome": ["threesomes"], "group": ["group_sex_orgy"], } HARDCORE_POSITION_KEY_MATCHES = { "missionary": ("missionary", "above her", "under her"), "cowgirl": ("cowgirl", "straddling", "straddles", "on top", "squatting on top"), "reverse_cowgirl": ("reverse cowgirl", "facing away"), "doggy": ("doggy", "all fours", "rear-entry", "from behind"), "bent_over": ("bent-over", "bent over", "hips raised"), "face_down_ass_up": ("face-down", "ass-up"), "standing": ("standing", "stands", "braced standing"), "side_lying": ("side-lying", "side lying", "spooning", "on the side", "on her side"), "edge_supported": ("edge-of-bed", "edge of bed", "bed edge", "raised edge", "edge-supported"), "kneeling": ("kneeling", "kneels", "kneeling center"), "lotus_lap": ("lotus", "lap", "seated in a partner's lap"), "face_sitting": ("face-sitting", "face sitting"), "sixty_nine": ("sixty-nine", "69"), "reclining_oral": ("reclining cunnilingus",), "straddled_oral": ("straddled oral",), "spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"), "chair_oral": ("chair oral",), "kissing": ("kiss", "kissing", "mouth-to-mouth", "mouth to mouth", "lips pressed"), "caressing": ("caress", "caressing", "hands roaming", "stroking skin", "hands sliding"), "breast_touch": ("breast", "breasts", "nipple", "cupping breasts", "touching breasts"), "face_touch": ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin"), "undressing": ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning"), "body_worship": ("body worship", "worship", "kissing down", "mouth on skin", "kissing the body"), "nipple_play": ("nipple", "nipples", "licking nipples", "sucking nipples", "nipple play"), "ass_grab": ("ass grab", "ass-grab", "ass grabbing", "hand on the ass", "squeezing the ass"), "thigh_kissing": ("thigh kiss", "thigh kissing", "kissing thighs", "mouth on inner thighs"), "hair_holding": ("hair holding", "hair held", "holding hair", "hair pulled back"), "wrist_pinning": ("wrist", "wrists", "pinning wrists", "wrists pinned", "hands pinned"), "dirty_talk": ("dirty talk", "whispering", "mouth near the ear", "telling", "verbal teasing"), "position_transition": ("transition", "turning around", "pulling onto the bed", "moving into position", "position change"), "guided_positioning": ("guiding", "guided", "guides", "lifting legs", "spreading thighs", "pulling hips", "turning the body"), "camera_showing": ("camera", "showing to camera", "presenting to camera", "spread open for camera", "creator-shot"), "watching": ("watching", "voyeur", "waiting turn", "partner watches", "onlooker"), "aftercare": ("aftercare", "cuddling", "kissing after", "holding close", "post-sex"), "cleanup": ("cleanup", "wiping", "cleaning", "towel", "wet cloth"), "fingering": ("fingering", "fingers inside", "fingers in pussy", "finger stimulation"), "clit_rubbing": ("clit", "clitoris", "clit rubbing", "rubbing the clit", "fingers on clit"), "mutual_masturbation": ("mutual masturbation", "both touching themselves", "masturbating together", "hands on their own bodies"), "boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"), "testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"), "penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"), "handjob": ("handjob", "hand job", "stroking the penis", "hand stroking", "manual stimulation"), "footjob": ("footjob", "soles", "toes curled", "feet stroking"), "open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"), "front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"), } HARDCORE_POSITION_AXIS_KEYS = { "position", "body_position", "body_arrangement", "arrangement", "tease_act", "touch_detail", "manual_act", "manual_detail", "worship_act", "transition_act", "control_act", "performance_act", "coordination_act", "aftercare_act", "cleanup_detail", } HARDCORE_SOURCE_FAMILY_BY_SUBCATEGORY = { "penetrative_sex": "penetrative", "foreplay_teasing": "foreplay", "body_worship_touching": "interaction", "clothing_position_transitions": "interaction", "dominant_guidance": "interaction", "camera_performance": "interaction", "manual_stimulation": "manual", "oral_sex": "oral", "outercourse_sex": "outercourse", "anal_double_penetration": "anal", "threesomes": "threesome", "group_coordination": "interaction", "group_sex_orgy": "group", "cumshot_climax": "climax", "aftercare_cleanup": "interaction", } FOCUS_FAMILY_BY_KEY = { "penetration_only": "penetrative", "foreplay_only": "foreplay", "interaction_only": "interaction", "manual_only": "manual", "oral_only": "oral", "outercourse_only": "outercourse", "anal_only": "anal", "climax_only": "climax", "threesome_only": "threesome", "group_only": "group", } def _is_false(value: Any) -> bool: if isinstance(value, bool): return value is False if isinstance(value, str): return value.strip().lower() in ("false", "0", "no", "off") return False def _list_from(value: Any) -> list[Any]: if value is None: return [] if isinstance(value, list): return value return [value] def _entry_text(item: Any) -> str: if isinstance(item, dict): return str( item.get("template") or item.get("prompt") or item.get("text") or item.get("description") or item.get("name") or "" ).strip() return str(item).strip() def _metadata_tokens(item: Any, keys: tuple[str, ...]) -> set[str]: if not isinstance(item, dict): return set() tokens: set[str] = set() for key in keys: for value in _list_from(item.get(key)): token = re.sub(r"[^a-z0-9]+", "_", str(value or "").strip().lower()).strip("_") if token and token != "any": tokens.add(token) return tokens def _entry_position_keys(item: Any) -> list[str]: if not isinstance(item, dict): return [] values: list[Any] = [] if item.get("position_keys") is not None: values.extend(_list_from(item.get("position_keys"))) if item.get("position_key") is not None: values.append(item.get("position_key")) return normalize_hardcore_position_values(values) def hardcore_position_family_choices() -> list[str]: return list(HARDCORE_POSITION_FAMILY_CHOICES) def hardcore_position_focus_choices() -> list[str]: return list(HARDCORE_POSITION_FOCUS_CHOICES) def hardcore_position_key_choices() -> list[str]: return list(HARDCORE_POSITION_KEY_CHOICES) def normalize_hardcore_position_family(value: Any, default: str = "any") -> str: text = re.sub(r"[^a-z0-9]+", "_", str(value or default).strip().lower()).strip("_") aliases = { "penetration": "penetrative", "penetrative_sex": "penetrative", "penetration_sex": "penetrative", "vaginal": "penetrative", "vaginal_penetration": "penetrative", "foreplay_teasing": "foreplay", "body_worship": "interaction", "body_worship_touching": "interaction", "clothing_position_transitions": "interaction", "dominant_guidance": "interaction", "camera_performance": "interaction", "group_coordination": "interaction", "aftercare_cleanup": "interaction", "manual_stimulation": "manual", "oral_sex": "oral", "outer_course": "outercourse", "outercourse_sex": "outercourse", "anal_double_penetration": "anal", "three_some": "threesome", "threesomes": "threesome", "group_sex": "group", "group_sex_orgy": "group", "orgy": "group", "cumshot": "climax", "cumshot_climax": "climax", "orgasm_aftermath": "climax", } text = aliases.get(text, text) return text if text in HARDCORE_POSITION_FAMILY_CHOICES else default def normalize_hardcore_position_values(values: Any) -> list[str]: raw_values = _list_from(values) selected: list[str] = [] for value in raw_values: text = str(value or "").strip() if not text or text == "any": continue normalized = re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_") if normalized in HARDCORE_POSITION_KEY_CHOICES and normalized not in selected: selected.append(normalized) return selected def empty_hardcore_position_config() -> dict[str, Any]: return { "config_type": "hardcore_position", "enabled": False, "family": "any", "positions": [], "require_position": False, "allow_toys": True, "allow_double": True, "allow_penetration": True, "allow_foreplay": True, "allow_interaction": True, "allow_manual": True, "allow_oral": True, "allow_outercourse": True, "allow_anal": True, "allow_climax": True, } def parse_hardcore_position_config(value: str | dict[str, Any] | None) -> dict[str, Any]: if not value: return empty_hardcore_position_config() if isinstance(value, dict): raw = value else: try: raw = json.loads(str(value)) except json.JSONDecodeError: return empty_hardcore_position_config() if not isinstance(raw, dict): return empty_hardcore_position_config() parsed = {**empty_hardcore_position_config(), **raw} parsed["enabled"] = bool(parsed.get("enabled", True)) parsed["family"] = normalize_hardcore_position_family(parsed.get("family")) parsed["positions"] = normalize_hardcore_position_values(parsed.get("positions")) parsed["require_position"] = not _is_false(parsed.get("require_position", False)) for key in ( "allow_toys", "allow_double", "allow_penetration", "allow_foreplay", "allow_interaction", "allow_manual", "allow_oral", "allow_outercourse", "allow_anal", "allow_climax", ): parsed[key] = not _is_false(parsed.get(key, True)) return parsed def hardcore_position_summary(config: dict[str, Any]) -> str: if not config.get("enabled"): return "hardcore position unrestricted" parts = [f"family={config.get('family', 'any')}"] positions = config.get("positions") or [] if positions: parts.append("positions=" + ",".join(positions)) elif config.get("require_position"): parts.append("position_templates=required") disabled = [ label for key, label in ( ("allow_toys", "toys"), ("allow_double", "double"), ("allow_penetration", "penetration"), ("allow_foreplay", "foreplay"), ("allow_interaction", "interaction"), ("allow_manual", "manual"), ("allow_oral", "oral"), ("allow_outercourse", "outercourse"), ("allow_anal", "anal"), ("allow_climax", "climax"), ) if not config.get(key, True) ] if disabled: parts.append("blocked=" + ",".join(disabled)) return "; ".join(parts) def build_hardcore_position_pool_json( hardcore_position_config: str | dict[str, Any] | None = "", combine_mode: str = "replace", family: str = "any", selected_positions: list[str] | tuple[str, ...] | str | None = None, ) -> str: base = parse_hardcore_position_config(hardcore_position_config) if combine_mode == "replace": base = {**empty_hardcore_position_config(), "enabled": True} else: base["enabled"] = True base["family"] = normalize_hardcore_position_family(family, base.get("family", "any")) selected = normalize_hardcore_position_values(selected_positions) if combine_mode == "add": existing = list(base.get("positions") or []) for value in selected: if value not in existing: existing.append(value) base["positions"] = existing else: base["positions"] = selected base["require_position"] = bool(base.get("require_position")) or bool(base["positions"]) or base["family"] != "any" base["summary"] = hardcore_position_summary(base) return json.dumps(base, ensure_ascii=True, sort_keys=True) def build_hardcore_action_filter_json( hardcore_position_config: str | dict[str, Any] | None = "", focus: str = "keep_pool", allow_toys: bool = False, allow_double: bool = False, allow_penetration: bool = True, allow_foreplay: bool = True, allow_interaction: bool = True, allow_manual: bool = True, allow_oral: bool = True, allow_outercourse: bool = True, allow_anal: bool = True, allow_climax: bool = True, ) -> str: config = parse_hardcore_position_config(hardcore_position_config) config["enabled"] = True focus = str(focus or "keep_pool").strip() focus_family = FOCUS_FAMILY_BY_KEY.get(focus) if focus_family: config["family"] = focus_family config["allow_toys"] = bool(allow_toys) config["allow_double"] = bool(allow_double) config["allow_penetration"] = bool(allow_penetration) config["allow_foreplay"] = bool(allow_foreplay) config["allow_interaction"] = bool(allow_interaction) config["allow_manual"] = bool(allow_manual) config["allow_oral"] = bool(allow_oral) config["allow_outercourse"] = bool(allow_outercourse) config["allow_anal"] = bool(allow_anal) config["allow_climax"] = bool(allow_climax) if not focus_family and config["family"] != "any": enabled_action_families = { family for enabled, family in ( (config["allow_penetration"], "penetrative"), (config["allow_foreplay"], "foreplay"), (config["allow_interaction"], "interaction"), (config["allow_manual"], "manual"), (config["allow_oral"], "oral"), (config["allow_outercourse"], "outercourse"), (config["allow_anal"], "anal"), (config["allow_climax"], "climax"), ) if enabled } if config["family"] in enabled_action_families and len(enabled_action_families) > 1: config["family"] = "any" if focus == "foreplay_only": config["allow_foreplay"] = True config["allow_interaction"] = True elif focus == "interaction_only": config["allow_interaction"] = True config["allow_foreplay"] = True elif focus == "manual_only": config["allow_manual"] = True elif focus == "oral_only": config["allow_oral"] = True config["allow_penetration"] = False elif focus == "outercourse_only": config["allow_outercourse"] = True config["allow_oral"] = False config["allow_penetration"] = False elif focus == "anal_only": config["allow_anal"] = True config["allow_penetration"] = True elif focus == "climax_only": config["allow_climax"] = True config["summary"] = hardcore_position_summary(config) return json.dumps(config, ensure_ascii=True, sort_keys=True) def hardcore_position_config_active(config: dict[str, Any]) -> bool: return bool(config.get("enabled")) def hardcore_position_template_required(config: dict[str, Any]) -> bool: if not hardcore_position_config_active(config): return False return ( bool(config.get("require_position")) or bool(config.get("positions")) or normalize_hardcore_position_family(config.get("family")) != "any" ) def hardcore_allowed_subcategory_slugs(config: dict[str, Any]) -> set[str]: family = normalize_hardcore_position_family(config.get("family")) base_allowed = set(HARDCORE_POSITION_FAMILY_SUBCATEGORIES.get(family, HARDCORE_POSITION_FAMILY_SUBCATEGORIES["any"])) allowed = set(base_allowed) if not config.get("allow_penetration", True): allowed.difference_update({"penetrative_sex", "anal_double_penetration", "threesomes", "group_sex_orgy"}) if not config.get("allow_foreplay", True): allowed.discard("foreplay_teasing") if not config.get("allow_interaction", True): allowed.difference_update( { "foreplay_teasing", "body_worship_touching", "clothing_position_transitions", "dominant_guidance", "camera_performance", "group_coordination", "aftercare_cleanup", } ) if not config.get("allow_manual", True): allowed.discard("manual_stimulation") if not config.get("allow_oral", True): allowed.discard("oral_sex") if not config.get("allow_outercourse", True): allowed.discard("outercourse_sex") if not config.get("allow_anal", True): allowed.discard("anal_double_penetration") if not config.get("allow_climax", True): allowed.discard("cumshot_climax") if not config.get("allow_double", True) and family == "anal": allowed.add("anal_double_penetration") if allowed: return allowed if family != "any": return base_allowed return set(HARDCORE_POSITION_FAMILY_SUBCATEGORIES["any"]) def is_hardcore_sexual_category(category: dict[str, Any]) -> bool: return ( str(category.get("slug") or "").strip() == "hardcore_sexual_poses" or str(category.get("name") or "").strip().lower() == "hardcore sexual poses" ) def hardcore_text_blocked_by_action(text: str, axis_name: str, config: dict[str, Any]) -> bool: text = str(text or "").lower() axis_name = str(axis_name or "").lower() if not config.get("allow_toys", True) and any(term in text for term in ("toy", "dildo", "strap-on", "strap on")): return True if not config.get("allow_double", True) and ( axis_name == "double_act" or any(term in text for term in ("double penetration", "double-penetration", "front-and-back", "front and back", "second penetration", "both sides", "two partners penetrating", "multiple penetrations")) ): return True if not config.get("allow_anal", True) and ( axis_name == "anal_act" or any(term in text for term in (" anal", "anal sex", "anal penetration", "anus", "rear-entry anal", "penis entering ass", "thrusts into her ass", "thrusts into his ass")) ): return True if not config.get("allow_oral", True) and ( axis_name in ("oral_act", "oral_detail") or any(term in text for term in ("oral sex", "mouth on genitals", "mouth on pussy", "blowjob", "cunnilingus", "tongue on pussy", "deepthroat", "fellatio")) ): return True if not config.get("allow_outercourse", True) and ( axis_name in ("outer_act", "contact_detail", "texture_detail") or any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex", "testicle", "balls", "penis licking", "penis-licking", "footjob", "soles", "toes")) ): return True if not config.get("allow_penetration", True) and ( axis_name in ("penetration_act", "penetration_detail", "anal_act", "double_act", "thrust_detail") or any(term in text for term in ("penetration", "penetrative", "thrust", "penis entering", "vaginal sex", "anal sex")) ): return True if not config.get("allow_foreplay", True) and ( axis_name in ("tease_act", "touch_detail", "clothing_detail", "foreplay_detail", "face_detail", "body_contact", "mood_detail") or any( term in text for term in ( "kiss", "kissing", "mouth-to-mouth", "caress", "caressing", "stroking skin", "hands roaming", "touching breasts", "cupping breasts", "hand on the cheek", "fingers under the chin", "undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning", ) ) ): return True if not config.get("allow_interaction", True) and ( axis_name in ( "tease_act", "touch_detail", "clothing_detail", "foreplay_detail", "face_detail", "body_contact", "mood_detail", "worship_act", "transition_act", "control_act", "performance_act", "coordination_act", "aftercare_act", "cleanup_detail", ) or any( term in text for term in ( "kiss", "kissing", "caress", "body worship", "nipple", "ass grab", "thigh", "hair holding", "wrists", "dirty talk", "whispering", "undressing", "position transition", "guided", "camera", "watching", "aftercare", "cleanup", "wiping", ) ) ): return True if not config.get("allow_manual", True) and ( axis_name in ("manual_act", "manual_detail") or any( term in text for term in ( "fingering", "fingers inside", "clit", "clitoris", "manual stimulation", "mutual masturbation", "masturbating together", "fingers on pussy", "fingers on clit", ) ) ): return True if not config.get("allow_climax", True) and ( axis_name in ("climax_act", "climax_hint", "climax_detail", "fluid_detail", "fluid_location") or any(term in text for term in ("climax", "cum", "semen", "ejaculat", "creampie", "post-orgasm", "post-penetration")) ): return True return False def hardcore_entry_blocked_by_action(entry: Any, axis_name: str, config: dict[str, Any]) -> bool: action_tokens = _metadata_tokens(entry, ("action_family", "action_type")) family_tokens = _metadata_tokens(entry, ("position_family", "family")) position_keys = set(_entry_position_keys(entry)) route_tokens = action_tokens | family_tokens if not config.get("allow_toys", True) and action_tokens & {"toy", "toy_double"}: return True if not config.get("allow_double", True) and (action_tokens & {"double", "toy_double"} or "front_back" in position_keys): return True if not config.get("allow_anal", True) and "anal" in route_tokens: return True if not config.get("allow_oral", True) and "oral" in route_tokens: return True if not config.get("allow_outercourse", True) and "outercourse" in route_tokens: return True if not config.get("allow_penetration", True) and route_tokens & {"penetration", "penetrative", "toy_double", "anal"}: return True if not config.get("allow_foreplay", True) and "foreplay" in route_tokens: return True if not config.get("allow_interaction", True) and "interaction" in route_tokens: return True if not config.get("allow_manual", True) and "manual" in route_tokens: return True if not config.get("allow_climax", True) and "climax" in route_tokens: return True return hardcore_text_blocked_by_action(_entry_text(entry), axis_name, config) def hardcore_position_entry_matches(entry: Any, config: dict[str, Any]) -> bool: positions = config.get("positions") or [] if not positions: return True metadata_keys = _entry_position_keys(entry) if metadata_keys: return bool(set(metadata_keys) & set(positions)) text = _entry_text(entry).lower() for position in positions: if any(term in text for term in HARDCORE_POSITION_KEY_MATCHES.get(position, ())): return True return False def hardcore_position_entry_conflicts(entry: Any, config: dict[str, Any]) -> bool: selected = set(config.get("positions") or []) if not selected: return False metadata_keys = _entry_position_keys(entry) if metadata_keys: matched = set(metadata_keys) else: text = _entry_text(entry).lower() matched = { position for position, terms in HARDCORE_POSITION_KEY_MATCHES.items() if any(term in text for term in terms) } return bool(matched) and not bool(matched & selected) def hardcore_subcategory_supports_positions(subcategory: dict[str, Any], config: dict[str, Any]) -> bool: if not hardcore_position_template_required(config): return True axes = subcategory.get("item_axes") if not isinstance(axes, dict): return True for axis_name, values in axes.items(): if str(axis_name) in HARDCORE_POSITION_AXIS_KEYS and any( hardcore_position_entry_matches(value, config) for value in _list_from(values) ): return True return False def filter_hardcore_axis(axis_name: str, values: list[Any], config: dict[str, Any]) -> list[Any]: if not hardcore_position_config_active(config): return values filtered = [ value for value in values if not hardcore_entry_blocked_by_action(value, axis_name, config) and not (axis_name not in HARDCORE_POSITION_AXIS_KEYS and hardcore_position_entry_conflicts(value, config)) and (axis_name not in HARDCORE_POSITION_AXIS_KEYS or hardcore_position_entry_matches(value, config)) ] return filtered or values def filter_hardcore_templates(templates: list[Any], config: dict[str, Any]) -> list[Any]: if not hardcore_position_config_active(config): return templates filtered: list[Any] = [] for template in templates: text = _entry_text(template) fields = {key for _, key, _, _ in Formatter().parse(text) if key} has_position_route = bool(fields & HARDCORE_POSITION_AXIS_KEYS) or bool(_entry_position_keys(template)) blocked = hardcore_position_template_required(config) and not has_position_route blocked = blocked or hardcore_entry_blocked_by_action(template, "", config) blocked = blocked or any(hardcore_text_blocked_by_action(text, field, config) for field in fields) if not blocked: filtered.append(template) return filtered or templates def apply_hardcore_position_config_to_subcategory( subcategory: dict[str, Any], config: dict[str, Any], ) -> dict[str, Any]: if not hardcore_position_config_active(config): return subcategory subcategory_copy = dict(subcategory) if "item_templates" in subcategory_copy: subcategory_copy["item_templates"] = filter_hardcore_templates(_list_from(subcategory_copy["item_templates"]), config) raw_axes = subcategory_copy.get("item_axes") if isinstance(raw_axes, dict): axes = {} for axis_name, values in raw_axes.items(): axes[axis_name] = filter_hardcore_axis(str(axis_name), _list_from(values), config) subcategory_copy["item_axes"] = axes subcategory_copy["hardcore_position_config"] = config return subcategory_copy def filter_hardcore_categories_for_position( categories: list[dict[str, Any]], config: dict[str, Any], women_count: int, men_count: int, compatible_entry: Callable[[dict[str, Any], int, int], bool], ) -> list[dict[str, Any]]: if not hardcore_position_config_active(config): return categories allowed = hardcore_allowed_subcategory_slugs(config) filtered_categories: list[dict[str, Any]] = [] for category in categories: if not is_hardcore_sexual_category(category): filtered_categories.append(category) continue category_copy = dict(category) subcategories = [ subcategory for subcategory in category.get("subcategories", []) if str(subcategory.get("slug") or "") in allowed and compatible_entry(subcategory, women_count, men_count) and hardcore_subcategory_supports_positions(subcategory, config) ] if subcategories: category_copy["subcategories"] = subcategories filtered_categories.append(category_copy) return filtered_categories def hardcore_source_position_family(subcategory: dict[str, Any], config: dict[str, Any] | None = None) -> str: slug = str(subcategory.get("slug") or subcategory.get("name") or "").strip().lower() family = HARDCORE_SOURCE_FAMILY_BY_SUBCATEGORY.get(slug, "") if family: return family config_family = normalize_hardcore_position_family((config or {}).get("family"), "") return "" if config_family == "any" else config_family def hardcore_position_keys(*parts: Any, axis_values: dict[str, Any] | None = None) -> list[str]: text = item_axis_policy.context_text(*parts, axis_values=axis_values) if not text: return [] keys: list[str] = [] for key, tokens in HARDCORE_POSITION_KEY_MATCHES.items(): if any(token in text for token in tokens): keys.append(key) return keys _normalize_hardcore_position_family = normalize_hardcore_position_family _normalize_hardcore_position_values = normalize_hardcore_position_values _empty_hardcore_position_config = empty_hardcore_position_config _parse_hardcore_position_config = parse_hardcore_position_config _hardcore_position_summary = hardcore_position_summary _hardcore_position_config_active = hardcore_position_config_active _hardcore_position_template_required = hardcore_position_template_required _hardcore_allowed_subcategory_slugs = hardcore_allowed_subcategory_slugs _hardcore_source_position_family = hardcore_source_position_family _hardcore_position_keys = hardcore_position_keys