Honor metadata in hardcore filters

This commit is contained in:
2026-06-27 14:19:47 +02:00
parent 7bc08ada47
commit ec79257613
2 changed files with 98 additions and 14 deletions
+70 -9
View File
@@ -240,6 +240,29 @@ def _entry_text(item: Any) -> str:
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)
@@ -633,10 +656,42 @@ def hardcore_text_blocked_by_action(text: str, axis_name: str, config: dict[str,
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, ())):
@@ -648,12 +703,16 @@ def hardcore_position_entry_conflicts(entry: Any, config: dict[str, Any]) -> boo
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)
}
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)
@@ -678,7 +737,7 @@ def filter_hardcore_axis(axis_name: str, values: list[Any], config: dict[str, An
filtered = [
value
for value in values
if not hardcore_text_blocked_by_action(_entry_text(value), axis_name, config)
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))
]
@@ -692,8 +751,10 @@ def filter_hardcore_templates(templates: list[Any], config: dict[str, Any]) -> l
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 | {""})
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