from __future__ import annotations import re from typing import Any try: from .krea_action_context import ( axis_values_text, is_climax_text, is_foreplay_text, is_oral_text, is_outercourse_text, is_toy_assisted_double_text, is_vaginal_penetration_text, ) except ImportError: # Allows local smoke tests with `python -c`. from krea_action_context import ( axis_values_text, is_climax_text, is_foreplay_text, is_oral_text, is_outercourse_text, is_toy_assisted_double_text, is_vaginal_penetration_text, ) ACTION_CLIMAX = "climax" ACTION_ANAL = "anal" ACTION_FOREPLAY = "foreplay" ACTION_MANUAL = "manual" ACTION_OUTERCOURSE = "outercourse" ACTION_ORAL = "oral" ACTION_PENETRATION = "penetration" ACTION_THREESOME = "threesome" ACTION_GROUP = "group" ACTION_TOY_DOUBLE = "toy_double" ACTION_DEFAULT = "default" HARDCORE_ACTION_FAMILY_CHOICES = { ACTION_CLIMAX, ACTION_ANAL, ACTION_FOREPLAY, ACTION_MANUAL, ACTION_OUTERCOURSE, ACTION_ORAL, ACTION_PENETRATION, ACTION_THREESOME, ACTION_GROUP, ACTION_TOY_DOUBLE, ACTION_DEFAULT, } def normalize_hardcore_action_family(value: Any, default: str = "") -> str: text = re.sub(r"[^a-z0-9]+", "_", str(value or "").strip().lower()).strip("_") aliases = { "penetrative": ACTION_PENETRATION, "penetrative_sex": ACTION_PENETRATION, "penetration_sex": ACTION_PENETRATION, "vaginal": ACTION_PENETRATION, "vaginal_penetration": ACTION_PENETRATION, "double": ACTION_TOY_DOUBLE, "double_penetration": ACTION_TOY_DOUBLE, "toy_double_penetration": ACTION_TOY_DOUBLE, "toy_assisted_double": ACTION_TOY_DOUBLE, "toy_assisted_double_penetration": ACTION_TOY_DOUBLE, "anal": ACTION_ANAL, "anal_sex": ACTION_ANAL, "anal_penetration": ACTION_ANAL, "outer_course": ACTION_OUTERCOURSE, "outercourse_sex": ACTION_OUTERCOURSE, "three_person": ACTION_THREESOME, "three_person_action": ACTION_THREESOME, "threesome": ACTION_THREESOME, "threesomes": ACTION_THREESOME, "threeway": ACTION_THREESOME, "three_way": ACTION_THREESOME, "group": ACTION_GROUP, "group_sex": ACTION_GROUP, "group_sex_orgy": ACTION_GROUP, "orgy": ACTION_GROUP, "manual": ACTION_MANUAL, "manual_stimulation": ACTION_MANUAL, "interaction": ACTION_FOREPLAY, "body_worship": ACTION_FOREPLAY, "body_worship_touching": ACTION_FOREPLAY, "foreplay_teasing": ACTION_FOREPLAY, "cumshot": ACTION_CLIMAX, "cumshot_climax": ACTION_CLIMAX, "orgasm_aftermath": ACTION_CLIMAX, "oral_sex": ACTION_ORAL, } text = aliases.get(text, text) return text if text in HARDCORE_ACTION_FAMILY_CHOICES else default def infer_hardcore_action_family( role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None, *, is_climax: bool | None = None, ) -> str: axis_text = axis_values_text(axis_values) if is_climax is None: is_climax = is_climax_text(role_graph, hard_item, composition, axis_text) if is_climax: return ACTION_CLIMAX if is_foreplay_text(role_graph, hard_item, composition, axis_text): return ACTION_FOREPLAY if is_outercourse_text(role_graph, hard_item, composition, axis_text): return ACTION_OUTERCOURSE if is_oral_text(role_graph, hard_item, composition, axis_text): return ACTION_ORAL if is_vaginal_penetration_text(role_graph, hard_item, composition, axis_text): return ACTION_PENETRATION if is_toy_assisted_double_text(role_graph, hard_item, composition, axis_text): return ACTION_TOY_DOUBLE return ACTION_DEFAULT def source_hardcore_action_family( source_family: Any, role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None, ) -> str: inferred = infer_hardcore_action_family(role_graph, hard_item, composition, axis_values) if inferred in (ACTION_CLIMAX, ACTION_TOY_DOUBLE): return inferred family = re.sub(r"[^a-z0-9]+", "_", str(source_family or "").strip().lower()).strip("_") family = { "penetration": "penetrative", "penetrative_sex": "penetrative", "outer_course": "outercourse", "outercourse_sex": "outercourse", "manual_stimulation": "manual", "foreplay_teasing": "foreplay", "body_worship": "interaction", "body_worship_touching": "interaction", "clothing_position_transitions": "interaction", "dominant_guidance": "interaction", "camera_performance": "interaction", "group_coordination": "interaction", "cumshot": "climax", "cumshot_climax": "climax", "oral_sex": "oral", }.get(family, family) source_mapping = { "penetrative": ACTION_PENETRATION, "anal": ACTION_ANAL, "foreplay": ACTION_FOREPLAY, "interaction": ACTION_FOREPLAY, "manual": ACTION_MANUAL, "oral": ACTION_ORAL, "outercourse": ACTION_OUTERCOURSE, "threesome": ACTION_THREESOME, "group": ACTION_GROUP, "climax": ACTION_CLIMAX, } return source_mapping.get(family, inferred)