138 lines
3.9 KiB
Python
138 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
OUTERCOURSE_BOOBJOB = "boobjob"
|
|
OUTERCOURSE_TESTICLE = "testicle_sucking"
|
|
OUTERCOURSE_PENIS_LICKING = "penis_licking"
|
|
OUTERCOURSE_HANDJOB = "handjob"
|
|
OUTERCOURSE_FOOTJOB = "footjob"
|
|
OUTERCOURSE_GENERIC = "generic"
|
|
|
|
|
|
OUTERCOURSE_ACTION_KIND_CHOICES = {
|
|
OUTERCOURSE_BOOBJOB,
|
|
OUTERCOURSE_TESTICLE,
|
|
OUTERCOURSE_PENIS_LICKING,
|
|
OUTERCOURSE_HANDJOB,
|
|
OUTERCOURSE_FOOTJOB,
|
|
OUTERCOURSE_GENERIC,
|
|
}
|
|
|
|
|
|
def _clean(value: Any) -> str:
|
|
text = "" if value is None else str(value)
|
|
text = text.replace("\n", " ")
|
|
text = re.sub(r"\s+", " ", text).strip()
|
|
text = re.sub(r"\s+([,.;:])", r"\1", text)
|
|
return text
|
|
|
|
|
|
def normalize_outercourse_action_kind(value: Any, default: str = OUTERCOURSE_GENERIC) -> str:
|
|
text = re.sub(r"[^a-z0-9]+", "_", _clean(value).lower()).strip("_")
|
|
aliases = {
|
|
"breast_sex": OUTERCOURSE_BOOBJOB,
|
|
"titjob": OUTERCOURSE_BOOBJOB,
|
|
"tit_job": OUTERCOURSE_BOOBJOB,
|
|
"testicle": OUTERCOURSE_TESTICLE,
|
|
"testicles": OUTERCOURSE_TESTICLE,
|
|
"ball_licking": OUTERCOURSE_TESTICLE,
|
|
"balls_licking": OUTERCOURSE_TESTICLE,
|
|
"balls": OUTERCOURSE_TESTICLE,
|
|
"penis_lick": OUTERCOURSE_PENIS_LICKING,
|
|
"penis_tongue": OUTERCOURSE_PENIS_LICKING,
|
|
"hand_job": OUTERCOURSE_HANDJOB,
|
|
"two_handed_handjob": OUTERCOURSE_HANDJOB,
|
|
"foot_job": OUTERCOURSE_FOOTJOB,
|
|
"feet_job": OUTERCOURSE_FOOTJOB,
|
|
}
|
|
text = aliases.get(text, text)
|
|
return text if text in OUTERCOURSE_ACTION_KIND_CHOICES else default
|
|
|
|
|
|
def infer_outercourse_action_kind(*parts: Any) -> str:
|
|
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
|
if not text:
|
|
return OUTERCOURSE_GENERIC
|
|
if any(
|
|
term in text
|
|
for term in (
|
|
"boobjob",
|
|
"titjob",
|
|
"tit job",
|
|
"breast sex",
|
|
"breast-sex",
|
|
"breasts tightly around",
|
|
"breasts around",
|
|
"breasts firmly together",
|
|
"penis squeezed between both breasts",
|
|
"penis shaft compressed between breasts",
|
|
"soft flesh squeezed around the penis",
|
|
)
|
|
):
|
|
return OUTERCOURSE_BOOBJOB
|
|
if any(
|
|
term in text
|
|
for term in (
|
|
"testicle",
|
|
"balls licking",
|
|
"balls-licking",
|
|
"balls held",
|
|
"balls close",
|
|
"balls and mouth",
|
|
"mouth and tongue on the viewer's balls",
|
|
"mouth and tongue on the pov viewer's balls",
|
|
"mouth and tongue licking the viewer's balls",
|
|
"mouth and tongue licking the pov viewer's balls",
|
|
)
|
|
):
|
|
return OUTERCOURSE_TESTICLE
|
|
if any(
|
|
term in text
|
|
for term in (
|
|
"penis licking",
|
|
"penis-licking",
|
|
"tongue along",
|
|
"tongue runs along",
|
|
"tongue running along",
|
|
"tongue licking",
|
|
"underside of the penis",
|
|
)
|
|
):
|
|
return OUTERCOURSE_PENIS_LICKING
|
|
if any(
|
|
term in text
|
|
for term in (
|
|
"handjob",
|
|
"hand job",
|
|
"hand wrapped",
|
|
"hand wraps around",
|
|
"hand stroking",
|
|
"both hands stroking",
|
|
"two-handed",
|
|
"one hand grips",
|
|
"one hand wrapped around",
|
|
)
|
|
):
|
|
return OUTERCOURSE_HANDJOB
|
|
if any(
|
|
term in text
|
|
for term in (
|
|
"footjob",
|
|
"foot job",
|
|
"soles",
|
|
"toes curled",
|
|
"feet stroking",
|
|
"feet and penis",
|
|
"both feet",
|
|
)
|
|
):
|
|
return OUTERCOURSE_FOOTJOB
|
|
return OUTERCOURSE_GENERIC
|
|
|
|
|
|
def outercourse_context_text(*parts: Any) -> str:
|
|
return " ".join(_clean(part).lower() for part in parts if _clean(part))
|