Move hardcore position filtering policy

This commit is contained in:
2026-06-27 03:02:23 +02:00
parent 1cc65e35b5
commit d4d3be5789
5 changed files with 343 additions and 246 deletions
+9 -235
View File
@@ -135,8 +135,6 @@ HARDCORE_POSITION_FAMILY_CHOICES = hardcore_position_policy.HARDCORE_POSITION_FA
HARDCORE_POSITION_FOCUS_CHOICES = hardcore_position_policy.HARDCORE_POSITION_FOCUS_CHOICES
HARDCORE_POSITION_KEY_CHOICES = hardcore_position_policy.HARDCORE_POSITION_KEY_CHOICES
HARDCORE_POSITION_FAMILY_SUBCATEGORIES = hardcore_position_policy.HARDCORE_POSITION_FAMILY_SUBCATEGORIES
HARDCORE_POSITION_KEY_MATCHES = hardcore_position_policy.HARDCORE_POSITION_KEY_MATCHES
HARDCORE_POSITION_AXIS_KEYS = hardcore_position_policy.HARDCORE_POSITION_AXIS_KEYS
HARDCORE_SOURCE_FAMILY_BY_SUBCATEGORY = hardcore_position_policy.HARDCORE_SOURCE_FAMILY_BY_SUBCATEGORY
@@ -997,16 +995,8 @@ def _hardcore_position_config_active(config: dict[str, Any]) -> bool:
return hardcore_position_policy.hardcore_position_config_active(config)
def _hardcore_position_template_required(config: dict[str, Any]) -> bool:
return hardcore_position_policy.hardcore_position_template_required(config)
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_allowed_subcategory_slugs(config: dict[str, Any]) -> set[str]:
return hardcore_position_policy.hardcore_allowed_subcategory_slugs(config)
return hardcore_position_policy.is_hardcore_sexual_category(category)
def _filter_hardcore_categories_for_position(
@@ -1015,236 +1005,20 @@ def _filter_hardcore_categories_for_position(
women_count: int,
men_count: int,
) -> 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_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_position_entry_matches(entry: Any, config: dict[str, Any]) -> bool:
positions = config.get("positions") or []
if not positions:
return True
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
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_text_blocked_by_action(_entry_text(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}
blocked = _hardcore_position_template_required(config) and not bool(fields & HARDCORE_POSITION_AXIS_KEYS)
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
return hardcore_position_policy.filter_hardcore_categories_for_position(
categories,
config,
women_count,
men_count,
_compatible_entry,
)
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
return hardcore_position_policy.apply_hardcore_position_config_to_subcategory(subcategory, config)
def _ratio_or_none(value: float) -> float | None: