345 lines
22 KiB
Python
345 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import random
|
|
import re
|
|
from typing import Any
|
|
|
|
try:
|
|
from .hardcore_role_anal import build_anal_or_double_role_graph
|
|
from .hardcore_role_oral import build_oral_role_graph
|
|
from .hardcore_role_outercourse import build_outercourse_role_graph
|
|
from .hardcore_role_penetration import build_penetration_role_graph
|
|
except ImportError: # Allows local smoke tests with `python -c`.
|
|
from hardcore_role_anal import build_anal_or_double_role_graph
|
|
from hardcore_role_oral import build_oral_role_graph
|
|
from hardcore_role_outercourse import build_outercourse_role_graph
|
|
from hardcore_role_penetration import build_penetration_role_graph
|
|
|
|
|
|
def _lettered(prefix: str, count: int) -> list[str]:
|
|
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
return [f"{prefix.capitalize()} {letters[index]}" for index in range(max(0, count))]
|
|
|
|
|
|
def _pick_distinct(rng: random.Random, items: list[str], count: int) -> list[str]:
|
|
if not items:
|
|
return []
|
|
if len(items) >= count:
|
|
return rng.sample(items, count)
|
|
picked = list(items)
|
|
while len(picked) < count:
|
|
picked.append(items[rng.randrange(len(items))])
|
|
return picked
|
|
|
|
|
|
def _participant_context(women_count: int, men_count: int) -> dict[str, list[str]]:
|
|
women = _lettered("woman", women_count)
|
|
men = _lettered("man", men_count)
|
|
return {"women": women, "men": men, "people": women + men}
|
|
|
|
|
|
def build_hardcore_role_graph(
|
|
rng: random.Random,
|
|
subcategory: dict[str, Any],
|
|
context: dict[str, Any],
|
|
item_axis_values: dict[str, Any] | None = None,
|
|
pov_labels: list[str] | None = None,
|
|
) -> str:
|
|
if context.get("subject_type") != "configured_cast":
|
|
return ""
|
|
women_count = int(context.get("women_count") or 0)
|
|
men_count = int(context.get("men_count") or 0)
|
|
people_count = women_count + men_count
|
|
if people_count <= 0:
|
|
return ""
|
|
|
|
participants = _participant_context(women_count, men_count)
|
|
women = participants["women"]
|
|
men = participants["men"]
|
|
people = participants["people"]
|
|
slug = str(subcategory.get("slug") or subcategory.get("name") or "").lower()
|
|
item_text = " ".join((item_axis_values or {}).values()).lower()
|
|
|
|
def any_person(exclude: set[str] | None = None) -> str:
|
|
exclude = exclude or set()
|
|
pool = [person for person in people if person not in exclude] or people
|
|
return rng.choice(pool)
|
|
|
|
def any_woman(exclude: set[str] | None = None) -> str:
|
|
exclude = exclude or set()
|
|
pool = [person for person in women if person not in exclude] or [person for person in people if person not in exclude] or people
|
|
return rng.choice(pool)
|
|
|
|
def any_man(exclude: set[str] | None = None) -> str:
|
|
exclude = exclude or set()
|
|
pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people
|
|
return rng.choice(pool)
|
|
|
|
def support_sentence(exclude: set[str]) -> str:
|
|
extras = [person for person in people if person not in exclude]
|
|
if not extras:
|
|
return ""
|
|
extra = rng.choice(extras)
|
|
actions = [
|
|
"kisses and grips the nearest body",
|
|
"holds hips open for the camera",
|
|
"touches breasts, thighs, and stomach",
|
|
"keeps one hand on a partner's ass",
|
|
"watches close and joins the body contact",
|
|
"presses in from the side with hands on skin",
|
|
]
|
|
return f" {extra} {rng.choice(actions)}."
|
|
|
|
def foreplay_position_graph(primary: str, partner: str) -> str:
|
|
text = " ".join(
|
|
str(part or "").lower()
|
|
for part in (
|
|
item_text,
|
|
*((item_axis_values or {}).values()),
|
|
)
|
|
)
|
|
if any(term in text for term in ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning")):
|
|
return (
|
|
f"{primary} and {partner} stand close while {partner}'s hands pull clothing aside from {primary}'s body; "
|
|
f"{primary}'s exposed skin and the clothing being removed stay clearly visible."
|
|
)
|
|
if any(term in text for term in ("breast", "breasts", "nipple", "cupping breasts", "touching breasts")):
|
|
return (
|
|
f"{primary} and {partner} press their bodies close while {partner}'s hand cups {primary}'s breast; "
|
|
f"their faces stay close and the breast-touching gesture is clear."
|
|
)
|
|
if any(term in text for term in ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin")):
|
|
return (
|
|
f"{primary} and {partner} stand face-to-face at close range while one hand holds {primary}'s cheek and jaw; "
|
|
f"their lips are close and the face-touching gesture is clear."
|
|
)
|
|
if any(term in text for term in ("kiss", "kissing", "mouth-to-mouth", "lips pressed")):
|
|
return (
|
|
f"{primary} and {partner} press their bodies together and kiss deeply, "
|
|
f"with hands on each other's face, waist, and hips."
|
|
)
|
|
return (
|
|
f"{primary} and {partner} are pressed close in a heated foreplay setup, "
|
|
f"hands caressing skin while clothing is pulled aside."
|
|
)
|
|
|
|
def interaction_text() -> str:
|
|
return " ".join(
|
|
str(part or "").lower()
|
|
for part in (
|
|
item_text,
|
|
*((item_axis_values or {}).values()),
|
|
)
|
|
)
|
|
|
|
def manual_position_graph(primary: str, partner: str = "") -> str:
|
|
text = interaction_text()
|
|
if not partner:
|
|
if "mutual" in text:
|
|
return f"{primary} faces the camera with thighs open, both hands on her body for solo mutual-style masturbation framing."
|
|
return f"{primary} reclines with thighs open, one hand between her legs and fingers visibly stimulating her pussy."
|
|
if "mutual" in text:
|
|
return f"{primary} and {partner} sit close facing each other, both touching themselves while keeping hands, faces, and bodies visible."
|
|
if "clit" in text or "clitoris" in text:
|
|
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers rubbing her clit as her hips tilt toward the touch."
|
|
if "toy" in text or "vibrator" in text:
|
|
return f"{primary} reclines with thighs open while {partner} holds a vibrator or toy against her clit, one hand keeping her thigh open."
|
|
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers visibly stimulating her pussy."
|
|
|
|
def interaction_position_graph(primary: str, partner: str, third: str = "") -> str:
|
|
text = interaction_text()
|
|
if "aftercare" in slug or any(term in text for term in ("aftercare", "cleanup", "wiping", "towel", "post-sex", "cuddle")):
|
|
if "cleanup" in text or "wiping" in text or "towel" in text:
|
|
return f"{primary} reclines after sex while {partner} kneels close and wipes her skin with a towel, hands and relaxed body contact visible."
|
|
return f"{primary} and {partner} lie close together after sex, bodies relaxed and hands resting on skin in a post-sex cuddle."
|
|
if "camera_performance" in slug or any(term in text for term in ("camera", "presenting", "showing", "viewer", "creator-shot")):
|
|
if third:
|
|
return f"{primary} faces the camera while {partner} and {third} hold and present her body, hands framing the exposed skin for the viewer."
|
|
return f"{primary} faces the camera and presents her body while {partner}'s hands hold her hips or thighs open for a clear creator-shot reveal."
|
|
if "body_worship" in slug or any(term in text for term in ("body worship", "nipple", "thigh", "mouth on skin", "kissing down", "ass grabbing")):
|
|
if "ass" in text:
|
|
return f"{primary} stands or kneels with hips angled back while {partner}'s hands grip her ass, fingers pressing into skin."
|
|
if "thigh" in text:
|
|
return f"{primary} reclines with thighs open while {partner} kneels close and kisses along her inner thighs, hands holding her legs in place."
|
|
if "nipple" in text or "breast" in text:
|
|
return f"{primary} arches toward {partner} while {partner}'s mouth is on her breast and one hand cups or squeezes the other breast."
|
|
return f"{primary} reclines or leans back while {partner} kisses down her body, hands tracing breasts, waist, hips, and thighs."
|
|
if "clothing_position" in slug or any(term in text for term in ("transition", "turning", "pulling onto", "lifting", "guided backward", "clothing", "garment")):
|
|
if "turn" in text or "rear-facing" in text:
|
|
return f"{partner}'s hands turn {primary} around by the hips, clothing partly moved aside as her body rotates into the next pose."
|
|
if "legs" in text or "thigh" in text:
|
|
return f"{primary} lies back while {partner} lifts and spreads her legs into position, hands and clothing movement clearly visible."
|
|
return f"{primary} and {partner} are mid-transition, with {partner}'s hands moving clothing aside and guiding {primary}'s hips toward the next pose."
|
|
if "dominant" in slug or any(term in text for term in ("hair", "wrist", "wrists", "jaw", "chin", "guided", "dominant", "control", "dirty talk", "whisper", "mouth near the ear", "verbal teasing")):
|
|
if "dirty talk" in text or "whisper" in text or "mouth near the ear" in text or "verbal teasing" in text:
|
|
return f"{partner} leans close to {primary}'s ear for dirty talk while holding her waist and keeping their bodies pressed close."
|
|
if "wrist" in text or "wrists" in text:
|
|
return f"{primary} lies back while {partner} pins her wrists above her head, both bodies close and the consensual control gesture clearly visible."
|
|
if "hair" in text:
|
|
return f"{partner} holds {primary}'s hair back while guiding her body closer, face and hair-hold gesture visible."
|
|
if "thigh" in text or "spread" in text:
|
|
return f"{primary} reclines with thighs open while {partner}'s hands spread her legs and hold the position for the camera."
|
|
return f"{partner} guides {primary}'s body with hands on her jaw, waist, and hips, keeping the consensual control gesture readable."
|
|
return foreplay_position_graph(primary, partner)
|
|
|
|
def group_coordination_graph(primary: str, partner: str, third: str) -> str:
|
|
observer = third or any_person({primary, partner})
|
|
text = interaction_text()
|
|
if "camera" in text or "hold" in text or "present" in text:
|
|
return f"{primary} is centered while {partner} and {observer} hold and present the body for the camera, each role clearly visible."
|
|
if "watch" in text or "waiting" in text:
|
|
return f"{primary} is centered while {partner} touches her body and {observer} watches close beside them, hands and faces readable."
|
|
return f"{primary} is centered while {partner} touches her body and {observer} stays close as the watching or guiding partner."
|
|
|
|
def mentions_ass(text: str) -> bool:
|
|
return bool(
|
|
re.search(
|
|
r"\bass\b|ass[- ](?:up|raised|exposed|lifted)|spread cheeks|lower back and ass|cum (?:on|dripping from) ass|pussy, ass|ass and",
|
|
text,
|
|
)
|
|
)
|
|
|
|
def climax_position_graph(woman: str, man: str, third: str = "") -> str:
|
|
if "lying between two partners" in item_text and third:
|
|
return f"{woman} lies between {man} and {third}, with {man} under her hips and {third} positioned above her torso as visible semen lands on her body."
|
|
if "held between front-and-back partners" in item_text and third:
|
|
return f"{woman} is held between {man} behind her and {third} in front of her as visible semen lands across her body."
|
|
if "kneeling between standing partners" in item_text and third:
|
|
return f"{woman} kneels between {man} and {third} while both stand close around her face and torso for visible ejaculation."
|
|
if "side-lying with thighs parted" in item_text:
|
|
return f"{woman} lies on her side with thighs parted while {man} kneels beside her hips and ejaculates semen across her thighs and pussy."
|
|
if "sitting on the edge of the bed" in item_text:
|
|
return f"{woman} sits on the edge of the bed with knees spread while {man} stands close between her legs and ejaculates semen across her body."
|
|
if "lying at the bed edge with thighs open" in item_text:
|
|
return f"{woman} lies at the bed edge with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs."
|
|
if "reclining with thighs open" in item_text or "lying on the back with legs spread" in item_text:
|
|
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs."
|
|
if "on all fours with hips raised" in item_text:
|
|
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and ejaculates semen across her ass, thighs, and lower back."
|
|
if "face-down ass-up" in item_text:
|
|
return f"{woman} lies face-down with ass raised while {man} is positioned behind her and ejaculates semen across her lower back and ass."
|
|
if "bent over with ass raised" in item_text or "bent over" in item_text:
|
|
return f"{woman} is bent forward with hips raised while {man} is positioned behind her, visible semen across her lower back, ass, and thighs."
|
|
if "kneeling with mouth open" in item_text:
|
|
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
|
|
if "kneeling in front of a standing partner" in item_text:
|
|
return f"{woman} kneels in front of {man} at hip height while {man} stands over her for visible ejaculation."
|
|
if "standing with cum on the body" in item_text:
|
|
return f"{woman} stands braced in front of {man} while he stays close at hip level and ejaculates semen across her body."
|
|
if "squatting on top of a partner" in item_text:
|
|
return f"{woman} squats over {man}'s hips while {man} lies on his back under her and ejaculates semen onto her body."
|
|
if "reverse cowgirl over a partner's hips" in item_text:
|
|
return f"{woman} straddles {man}'s hips facing away while {man} lies on his back under her and ejaculates semen onto her body."
|
|
if any(term in item_text for term in ("straddling a partner", "straddling a partner's hips", "shared climax after penetration", "orgasm during penetration")):
|
|
return f"{woman} straddles {man}'s hips while {man} lies on his back under her, their bodies still aligned from penetration as he ejaculates semen onto her body."
|
|
if "seated in a partner's lap facing them" in item_text:
|
|
return f"{woman} sits in {man}'s lap facing him, legs wrapped around his hips as he ejaculates semen across her body."
|
|
if any(term in item_text for term in ("lower back", "cum dripping from ass", "cum on lower back")) or mentions_ass(item_text):
|
|
return f"{woman} is bent forward with hips raised while {man} is positioned behind her, visible semen across her lower back, ass, and thighs."
|
|
if any(term in item_text for term in ("cum on face", "cum on tongue", "cum on lips", "cum on face and lips", "cum on tongue and chin")):
|
|
if third:
|
|
return f"{woman} kneels in the center while {man} and {third} stand close around her face and torso for visible ejaculation."
|
|
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
|
|
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen onto her body."
|
|
|
|
if people_count == 1:
|
|
solo = people[0]
|
|
if women_count == 1:
|
|
if "manual_stimulation" in slug:
|
|
return manual_position_graph(solo)
|
|
if "camera_performance" in slug:
|
|
return f"{solo} faces the camera and presents her body with hands framing the exposed skin in a solo creator-shot pose."
|
|
if "cumshot" in slug or "climax" in slug:
|
|
return f"{solo} is shown in a solo explicit orgasm pose with thighs open, one hand on her body, and visible arousal on skin and sheets."
|
|
return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness."
|
|
if "cumshot" in slug or "climax" in slug:
|
|
return f"{solo} is shown in a solo visible ejaculation pose with one hand on his penis, body angled toward the camera, and semen visible."
|
|
return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing."
|
|
|
|
if women_count > 0 and men_count == 0:
|
|
a, b = _pick_distinct(rng, women, 2)
|
|
c = any_woman({a, b}) if len(women) >= 3 else ""
|
|
used = {a, b}
|
|
if "manual_stimulation" in slug:
|
|
graph = manual_position_graph(a, b)
|
|
elif "group_coordination" in slug and c:
|
|
graph = group_coordination_graph(a, b, c)
|
|
used.add(c)
|
|
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
|
graph = interaction_position_graph(a, b, c)
|
|
if c and "camera_performance" in slug:
|
|
used.add(c)
|
|
elif "foreplay" in slug:
|
|
graph = foreplay_position_graph(a, b)
|
|
elif "outercourse" in slug:
|
|
graph = f"{a} kneels close to {b}'s body and uses mouth, hands, breasts, or feet for explicit non-penetrative contact."
|
|
elif "oral" in slug:
|
|
graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy."
|
|
elif "anal" in slug or "double" in slug:
|
|
graph = f"{a} uses a strap-on on {b} while keeping her hips held open."
|
|
elif "threesome" in slug or "group" in slug or "orgy" in slug:
|
|
helper = c or any_woman({a})
|
|
graph = f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies."
|
|
used.add(helper)
|
|
elif "cumshot" in slug or "climax" in slug:
|
|
graph = f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets."
|
|
else:
|
|
graph = f"{a} uses a strap-on on {b} while their bodies stay pressed together."
|
|
return graph + support_sentence(used)
|
|
|
|
if men_count > 0 and women_count == 0:
|
|
a, b = _pick_distinct(rng, men, 2)
|
|
c = any_man({a, b}) if len(men) >= 3 else ""
|
|
used = {a, b}
|
|
if "manual_stimulation" in slug:
|
|
graph = f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup."
|
|
elif "group_coordination" in slug and c:
|
|
graph = group_coordination_graph(a, b, c)
|
|
used.add(c)
|
|
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
|
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
|
|
elif "foreplay" in slug:
|
|
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
|
|
elif "outercourse" in slug:
|
|
graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet."
|
|
elif "oral" in slug:
|
|
graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips."
|
|
elif "anal" in slug or "double" in slug or "penetrative" in slug:
|
|
graph = f"{a} penetrates {b} anally while {b}'s hips are held open."
|
|
elif "threesome" in slug or "group" in slug or "orgy" in slug:
|
|
helper = c or any_man({a})
|
|
graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front."
|
|
used.add(helper)
|
|
elif "cumshot" in slug or "climax" in slug:
|
|
graph = f"{a} ejaculates semen over {b}'s body while {b} keeps eye contact and one hand on his penis."
|
|
else:
|
|
graph = f"{a} and {b} keep explicit penis and anal contact visible."
|
|
return graph + support_sentence(used)
|
|
|
|
woman = any_woman()
|
|
man = any_man()
|
|
third = any_person({woman, man}) if people_count >= 3 else ""
|
|
if "manual_stimulation" in slug:
|
|
graph = manual_position_graph(woman, man)
|
|
elif "group_coordination" in slug:
|
|
graph = group_coordination_graph(woman, man, third)
|
|
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
|
graph = interaction_position_graph(woman, man, third)
|
|
elif "foreplay" in slug:
|
|
graph = foreplay_position_graph(woman, man)
|
|
elif "outercourse" in slug:
|
|
graph = build_outercourse_role_graph(woman, man, item_text, item_axis_values, pov_labels)
|
|
elif "oral" in slug:
|
|
graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels)
|
|
elif "anal" in slug or "double" in slug:
|
|
graph = build_anal_or_double_role_graph(woman, man, third, people_count, item_text, item_axis_values)
|
|
elif "threesome" in slug:
|
|
graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body."
|
|
elif "group" in slug or "orgy" in slug:
|
|
graph = f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs."
|
|
elif "cumshot" in slug or "climax" in slug:
|
|
graph = climax_position_graph(woman, man, third)
|
|
else:
|
|
graph = build_penetration_role_graph(woman, man, item_text, item_axis_values)
|
|
return graph + support_sentence({woman, man, third} if third else {woman, man})
|