Move hardcore position filtering policy
This commit is contained in:
+262
-1
@@ -2,7 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
from string import Formatter
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
HARDCORE_POSITION_FAMILY_CHOICES = [
|
||||
@@ -226,6 +227,19 @@ def _list_from(value: Any) -> list[Any]:
|
||||
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 hardcore_position_family_choices() -> list[str]:
|
||||
return list(HARDCORE_POSITION_FAMILY_CHOICES)
|
||||
|
||||
@@ -486,6 +500,253 @@ def hardcore_allowed_subcategory_slugs(config: dict[str, Any]) -> set[str]:
|
||||
return allowed or 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_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
|
||||
|
||||
|
||||
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, "")
|
||||
|
||||
Reference in New Issue
Block a user