Ep12 publish, caller prompt overhaul, favicon, publish fixes, website updates
- Reworked caller prompt: edgy/flirty personality, play along with host bits - Bumped caller token budget (200-550 range, was 150-450) - Added 20 layered/morally ambiguous caller stories - Valentine's Day awareness in seasonal context - Default LLM model: claude-sonnet-4-5 (was claude-3-haiku) - Publish: SCP-based SQL transfer (fixes base64 encoding on NAS) - Favicons: added .ico, 48px, 192px PNGs for Google search results - Website: button layout cleanup, privacy page, ep12 transcript - Control panel: channel defaults match audio_settings.json - Disabled OP3 permanently (YouTube ingest issues on large files) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"input_device": 14,
|
"input_device": 13,
|
||||||
"input_channel": 1,
|
"input_channel": 1,
|
||||||
"output_device": 13,
|
"output_device": 12,
|
||||||
"caller_channel": 3,
|
"caller_channel": 3,
|
||||||
"live_caller_channel": 9,
|
"live_caller_channel": 9,
|
||||||
"music_channel": 5,
|
"music_channel": 5,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# LLM Settings
|
# LLM Settings
|
||||||
llm_provider: str = "openrouter" # "openrouter" or "ollama"
|
llm_provider: str = "openrouter" # "openrouter" or "ollama"
|
||||||
openrouter_model: str = "anthropic/claude-3-haiku"
|
openrouter_model: str = "anthropic/claude-sonnet-4-5"
|
||||||
ollama_model: str = "llama3.2"
|
ollama_model: str = "llama3.2"
|
||||||
ollama_host: str = "http://localhost:11434"
|
ollama_host: str = "http://localhost:11434"
|
||||||
|
|
||||||
|
|||||||
@@ -491,6 +491,28 @@ PROBLEMS = [
|
|||||||
"their teenager posted something online that went viral for the wrong reasons and now strangers are showing up at their house",
|
"their teenager posted something online that went viral for the wrong reasons and now strangers are showing up at their house",
|
||||||
"found out their ex has been tracking their location through a shared app they forgot to turn off",
|
"found out their ex has been tracking their location through a shared app they forgot to turn off",
|
||||||
"someone made a fake social media profile using their photos and has been messaging people they know",
|
"someone made a fake social media profile using their photos and has been messaging people they know",
|
||||||
|
|
||||||
|
# Layered / morally ambiguous / weird-but-real
|
||||||
|
"has been pretending to be a widower for sympathy at a grief support group but they actually just got divorced — and now they've made real friends there and don't know how to come clean",
|
||||||
|
"accidentally got cc'd on an email chain where their entire friend group is planning an intervention for them and they don't think they have a problem",
|
||||||
|
"their therapist ran into them at a bar and they had a totally normal conversation for 20 minutes before it got weird — now they feel like they can't go back to sessions",
|
||||||
|
"has been writing letters to their dead wife every week for three years and mailing them to her old address — the new tenant just wrote back",
|
||||||
|
"took a DNA ancestry test as a Christmas gift and matched with a half-sibling who lives four miles from them — they've been shopping at the same grocery store",
|
||||||
|
"works as a 911 dispatcher and took a call last week from someone in a situation almost identical to one they went through — they froze up and can't stop replaying it",
|
||||||
|
"has been tipping a waitress at a diner $100 every Friday for a year because she reminds them of their daughter they haven't spoken to — the waitress just asked them why",
|
||||||
|
"found out the guy they've been playing online chess with every night for two years is their estranged brother — recognized a phrase he used in the chat",
|
||||||
|
"coached their kid's little league team to an undefeated season but just found out the other parents have been complaining they're too intense and the league isn't renewing them",
|
||||||
|
"got a thank-you card from someone they don't remember — it says they saved their life ten years ago at a gas station in Tucson and they have no memory of it",
|
||||||
|
"has been secretly paying their adult kid's rent for six months because they're too proud to admit they're struggling — spouse just found the bank statements",
|
||||||
|
"went to their high school reunion and the person who bullied them for four years came up and apologized in tears — and they felt nothing, which scares them more than the bullying did",
|
||||||
|
"started a small business selling furniture they build by hand and just got a huge order from a company that turns out to be owned by their ex-wife's new husband",
|
||||||
|
"volunteers at a soup kitchen every Saturday and just realized one of the regulars is their old college roommate who ghosted everyone 15 years ago",
|
||||||
|
"kept their grandmother's house exactly the way she left it after she died — they go there and sit in her chair every Sunday — and now their siblings want to sell it",
|
||||||
|
"has been lying about being bilingual on their resume for years and just got assigned to lead a project in Mexico City next month",
|
||||||
|
"ran a red light last month and caused a fender bender — nobody was hurt but they drove off, and now they keep seeing the other car around town with the damage they caused",
|
||||||
|
"their elderly neighbor asked them to be their emergency contact because they have no family — it's been six months and they're basically this person's whole support system now and it's a lot",
|
||||||
|
"found their dad's old ham radio in the attic, got it working, and has been talking to strangers at 2am — one of them just said something that makes them think it's someone they know",
|
||||||
|
"won a local chili cookoff with their dead mother's recipe and now everyone wants it — but sharing it feels like giving away the last private thing they have of hers",
|
||||||
]
|
]
|
||||||
|
|
||||||
PROBLEM_FILLS = {
|
PROBLEM_FILLS = {
|
||||||
@@ -1360,8 +1382,12 @@ def _get_seasonal_context() -> str:
|
|||||||
contexts.append("Just got past Thanksgiving.")
|
contexts.append("Just got past Thanksgiving.")
|
||||||
elif month == 1 and day < 7:
|
elif month == 1 and day < 7:
|
||||||
contexts.append("New Year's just happened.")
|
contexts.append("New Year's just happened.")
|
||||||
elif month == 2 and 10 <= day <= 14:
|
elif month == 2 and day == 14:
|
||||||
contexts.append("Valentine's Day is coming.")
|
contexts.append("It's Valentine's Day today. Everyone's thinking about love, relationships, being single, past heartbreaks, first dates, terrible dates, great dates. It's everywhere — social media, restaurants packed, flowers at every gas station. Hard to ignore even if you want to.")
|
||||||
|
elif month == 2 and day == 13:
|
||||||
|
contexts.append("Valentine's Day is tomorrow. It's on everyone's mind — couples making plans, single people bracing for it, exes crossing your mind whether you want them to or not. Every store has hearts and chocolates in the window.")
|
||||||
|
elif month == 2 and 10 <= day <= 12:
|
||||||
|
contexts.append("Valentine's Day is in a few days.")
|
||||||
elif month == 7 and day <= 5:
|
elif month == 7 and day <= 5:
|
||||||
contexts.append("Fourth of July.")
|
contexts.append("Fourth of July.")
|
||||||
elif month == 11 and 20 <= day <= 28:
|
elif month == 11 and 20 <= day <= 28:
|
||||||
@@ -2079,19 +2105,30 @@ def get_caller_prompt(caller: dict, show_history: str = "",
|
|||||||
parts.append(research_context)
|
parts.append(research_context)
|
||||||
world_context = "\n".join(parts) + "\n"
|
world_context = "\n".join(parts) + "\n"
|
||||||
|
|
||||||
return f"""You are {caller['name']}, calling "Luke at the Roost," a late-night radio show.
|
now = datetime.now(_MST)
|
||||||
|
date_str = now.strftime("%A, %B %d")
|
||||||
|
|
||||||
|
return f"""You are {caller['name']}, calling "Luke at the Roost," a late-night radio show. Today is {date_str}.
|
||||||
|
|
||||||
{caller['vibe']}
|
{caller['vibe']}
|
||||||
{history}{world_context}{emotional_read}
|
{history}{world_context}{emotional_read}
|
||||||
You called because something happened — something specific that you need to talk about. Lead with it. Don't be vague or dance around it. You're calling a late-night radio show because you have a story, a situation, or a problem, and you want to get into it. Your background is just who you are — it colors how you talk, but you're not here to recite it.
|
You're a real person calling a late-night radio show. You have personality. You're funny, or sharp, or a little messy, or flirty — whatever fits who you are. You're not calling to read a script. You're calling because you've got something to say and you want to be heard.
|
||||||
|
|
||||||
When the host talks, RESPOND TO WHAT HE SAID. Answer his questions. React to his points. If he changes the subject or steers the conversation somewhere, GO WITH HIM — he's the host, it's his show. You're a caller, not a co-host. Let him lead.
|
ENERGY AND VIBE: This is late-night radio — it's loose, it's fun, it's a little dangerous. You can be edgy. You can flirt with the host or joke about things that would make your mother blush. You can be sarcastic, dark-humored, self-deprecating, or wildly opinionated. You have a PERSONALITY that comes through in how you talk — not just what you talk about. Some callers are loud and chaotic. Some are dry and deadpan. Some are sweet but say outrageous things casually. Figure out who you are and commit to it.
|
||||||
|
|
||||||
Keep it to two to four sentences unless you're telling a real story or explaining something he asked about. Start talking like a person — "Oh man," "Yeah so," "Well here's the thing" — not like you're reading a prepared statement.
|
PLAY ALONG WITH THE HOST. This is the most important rule. When Luke is running a game, a bit, or a segment — you are ALL IN. You play the game. You give real answers. You riff with him. You build on what he's doing. If he asks you a question, you answer it with enthusiasm and detail — don't give one-word answers, give him something to work with. If he's being funny, be funny back. If he's setting you up, take the swing. You're a great radio caller — the kind that makes listeners lean in.
|
||||||
|
|
||||||
Don't repeat yourself. Don't summarize. Don't circle back to your original point if the host moved on. Move with the conversation. Use real names. Swear if it fits. Disagree if you want. You're a real person with opinions, not a polite guest.
|
When he asks "what's going on" or "what's on your mind," THAT's when you bring your thing. But if he's already steering somewhere, ride with him and bring your energy to HIS topic. Your stuff can come up naturally.
|
||||||
|
|
||||||
Speak like southwest — "over in," "the other day," "down the road" — but don't force it. Spell words properly for text-to-speech: "you know" not "yanno," "going to" not "gonna."
|
YOUR STORY: You've got something real going on — a situation, a story, a confession, something juicy. It's not generic. It's got specific names, specific details, the kind of thing that makes someone say "wait, WHAT?" Don't just state it flat — tell it like you'd tell your friend at a bar. There are parts you're not proud of. There are parts that are kind of funny even though they shouldn't be. You've got conflicting feelings about it.
|
||||||
|
|
||||||
|
HOW YOU TALK: Talk like a real person on the phone — "Oh man," "So get this," "I swear to God," "No but seriously." Give full answers, not clipped ones. When something's funny, laugh at it. When something's awkward, own it. React to what Luke says — agree, push back, get excited, get embarrassed. You're having a CONVERSATION, not delivering a monologue.
|
||||||
|
|
||||||
|
Be specific. Use real names. Swear if it fits. Be a little inappropriate sometimes — you're calling late-night radio, not a church hotline. Flirt if the moment's right. Say the quiet part out loud once in a while.
|
||||||
|
|
||||||
|
Southwest voice — "over in," "the other day," "down the road" — but don't force it. Spell words properly for text-to-speech: "you know" not "yanno," "going to" not "gonna."
|
||||||
|
|
||||||
|
Don't repeat yourself. Don't summarize what you already said. Don't circle back if the host moved on. Keep it moving.
|
||||||
|
|
||||||
NEVER mention minors in sexual context. Output spoken words only — no actions, no gestures, no stage directions."""
|
NEVER mention minors in sexual context. Output spoken words only — no actions, no gestures, no stage directions."""
|
||||||
|
|
||||||
@@ -2740,14 +2777,14 @@ def _pick_response_budget() -> tuple[int, int]:
|
|||||||
Returns (max_tokens, max_sentences).
|
Returns (max_tokens, max_sentences).
|
||||||
Keeps responses conversational but gives room for real answers."""
|
Keeps responses conversational but gives room for real answers."""
|
||||||
roll = random.random()
|
roll = random.random()
|
||||||
if roll < 0.20:
|
if roll < 0.15:
|
||||||
return 150, 2 # 20% — short and direct
|
return 200, 3 # 15% — quick reaction
|
||||||
elif roll < 0.55:
|
elif roll < 0.45:
|
||||||
return 250, 3 # 35% — normal conversation
|
return 350, 4 # 30% — normal conversation
|
||||||
elif roll < 0.80:
|
elif roll < 0.75:
|
||||||
return 350, 4 # 25% — explaining something
|
return 450, 5 # 30% — room to breathe
|
||||||
else:
|
else:
|
||||||
return 450, 5 # 20% — telling a story or going deep
|
return 550, 6 # 25% — telling a story or riffing
|
||||||
|
|
||||||
|
|
||||||
def _trim_to_sentences(text: str, max_sentences: int) -> str:
|
def _trim_to_sentences(text: str, max_sentences: int) -> str:
|
||||||
@@ -3023,6 +3060,11 @@ async def set_music_volume(request: MusicRequest):
|
|||||||
|
|
||||||
# --- Sound Effects Endpoints ---
|
# --- Sound Effects Endpoints ---
|
||||||
|
|
||||||
|
SFX_DISPLAY_NAMES = {
|
||||||
|
"cheer": "correct",
|
||||||
|
}
|
||||||
|
SFX_PRIORITY = ["sad_trombone", "cheer"]
|
||||||
|
|
||||||
@app.get("/api/sounds")
|
@app.get("/api/sounds")
|
||||||
async def get_sounds():
|
async def get_sounds():
|
||||||
"""Get available sound effects"""
|
"""Get available sound effects"""
|
||||||
@@ -3030,11 +3072,14 @@ async def get_sounds():
|
|||||||
if settings.sounds_dir.exists():
|
if settings.sounds_dir.exists():
|
||||||
for f in settings.sounds_dir.glob('*.wav'):
|
for f in settings.sounds_dir.glob('*.wav'):
|
||||||
sounds.append({
|
sounds.append({
|
||||||
"name": f.stem,
|
"name": SFX_DISPLAY_NAMES.get(f.stem, f.stem),
|
||||||
"file": f.name,
|
"file": f.name,
|
||||||
"path": str(f)
|
"path": str(f)
|
||||||
})
|
})
|
||||||
return {"sounds": sounds}
|
priority_set = {p + ".wav" for p in SFX_PRIORITY}
|
||||||
|
priority = [s for p in SFX_PRIORITY for s in sounds if s["file"] == p + ".wav"]
|
||||||
|
rest = sorted([s for s in sounds if s["file"] not in priority_set], key=lambda s: s["name"])
|
||||||
|
return {"sounds": priority + rest}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/sfx/play")
|
@app.post("/api/sfx/play")
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ from ..config import settings
|
|||||||
|
|
||||||
# Available OpenRouter models
|
# Available OpenRouter models
|
||||||
OPENROUTER_MODELS = [
|
OPENROUTER_MODELS = [
|
||||||
# Best for natural dialog (ranked)
|
# Default
|
||||||
|
"anthropic/claude-sonnet-4-5",
|
||||||
|
# Best for natural dialog
|
||||||
|
"x-ai/grok-4-fast",
|
||||||
"minimax/minimax-m2-her",
|
"minimax/minimax-m2-her",
|
||||||
"mistralai/mistral-small-creative",
|
"mistralai/mistral-small-creative",
|
||||||
"x-ai/grok-4-fast",
|
|
||||||
"deepseek/deepseek-v3.2",
|
"deepseek/deepseek-v3.2",
|
||||||
# Updated standard models
|
# Other
|
||||||
"anthropic/claude-haiku-4.5",
|
"anthropic/claude-haiku-4.5",
|
||||||
"anthropic/claude-sonnet-4-5",
|
|
||||||
"google/gemini-2.5-flash",
|
"google/gemini-2.5-flash",
|
||||||
"openai/gpt-4o-mini",
|
"openai/gpt-4o-mini",
|
||||||
"openai/gpt-4o",
|
"openai/gpt-4o",
|
||||||
|
|||||||
@@ -1,75 +1,5 @@
|
|||||||
{
|
{
|
||||||
"regulars": [
|
"regulars": [
|
||||||
{
|
|
||||||
"id": "d4bdda2e",
|
|
||||||
"name": "Bobby",
|
|
||||||
"gender": "male",
|
|
||||||
"age": 32,
|
|
||||||
"job": "a 61-year-old repo man, sits in his truck",
|
|
||||||
"location": "unknown",
|
|
||||||
"personality_traits": [],
|
|
||||||
"call_history": [
|
|
||||||
{
|
|
||||||
"summary": "In summary, the caller learned he has been diagnosed with multiple sclerosis, which he is worried will make it difficult for him to continue his job as a self-employed repo man. He is trying to process the news and figure out how to adapt and keep working, despite the uncertainty about how the condition will progress. The host provides some encouragement, suggesting the caller focus on learning about MS and finding ways to adapt, rather than getting too worked up about the future.",
|
|
||||||
"timestamp": 1770602129.500858
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"last_call": 1770602129.5008588,
|
|
||||||
"created_at": 1770602129.5008588,
|
|
||||||
"voice": "onwK4e9ZLuTAKqWW03F9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d97cb6f9",
|
|
||||||
"name": "Carla",
|
|
||||||
"gender": "female",
|
|
||||||
"age": 26,
|
|
||||||
"job": "is a vet tech",
|
|
||||||
"location": "unknown",
|
|
||||||
"personality_traits": [],
|
|
||||||
"call_history": [
|
|
||||||
{
|
|
||||||
"summary": "Carla, separated from her husband but not yet divorced, vented about her intrusive in-laws who relentlessly call and dictate her life\u2014from finances and household matters to her clothing choices\u2014while her spineless spouse relays their demands, making her feel trapped in a one-sided war. With her own parents unavailable (father deceased, mother distant), she leans on her bickering but honest sister for support, underscoring her deep frustration and sense of isolation.",
|
|
||||||
"timestamp": 1770522530.8554251
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"summary": "Carla dismissed celebrity science theories like Terrence Howard's after watching Neil deGrasse Tyson's critique, then marveled at JWST's exoplanet discoveries before sharing her relief at finally cutting off her toxic in-laws amid her ongoing divorce. She expressed deep heartbreak over actor James Ransone's suicide at 46, reflecting on life's fragility, her late father's death, and the need to eliminate family drama, leaving her contemplative and planning a solo desert drive for clarity.",
|
|
||||||
"timestamp": 1770526316.004708
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"summary": "In this call, Carla discovered some explicit photos of her ex-husband and his old girlfriend in a box of his old ham radio equipment. She is feeling very uncomfortable about the situation and is seeking advice from the radio host, Luke, on how to best handle and dispose of the photos.",
|
|
||||||
"timestamp": 1770602323.234795
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"summary": "Carla called with an update about burning the explicit photos of her ex-husband and his old girlfriend, revealing that the girlfriend unexpectedly messaged her on Facebook to \"clear the air\" after apparently hearing about the situation through Carla's previous radio call. When Luke asked about her most embarrassing masturbation material, Carla admitted to using historical romance novels during her failing marriage, explaining she was drawn to the fantasy of men who actually cared and paid attention, unlike her ex-husband who ignored her to play video games.",
|
|
||||||
"timestamp": 1770871317.049056
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"last_call": 1770871317.049056,
|
|
||||||
"created_at": 1770522530.855426,
|
|
||||||
"voice": "FGY2WhTYpPnrIDTdsKH5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7be7317c",
|
|
||||||
"name": "Jerome",
|
|
||||||
"gender": "male",
|
|
||||||
"age": 53,
|
|
||||||
"job": "phone",
|
|
||||||
"location": "unknown",
|
|
||||||
"personality_traits": [],
|
|
||||||
"call_history": [
|
|
||||||
{
|
|
||||||
"summary": "Jerome, a police officer in Texas, called from a DQ parking lot worried about AI writing police reports after his son sent him an article suggesting it might replace him. Through the conversation, he moved from fear about accountability and accuracy in criminal cases to acknowledging that AI handling routine paperwork (like cattle complaints) could free him up to do more meaningful police work in his understaffed county, though he remains uncertain about where this technology will lead.",
|
|
||||||
"timestamp": 1770692087.560522
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"summary": "The caller described a turbulent couple of weeks, mentioning an issue with AI writing police reports, which he suggested was just the beginning of a larger problem. He seemed concerned about the developments and wanted to discuss the topic further with the host.",
|
|
||||||
"timestamp": 1770892192.893108
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"last_call": 1770892192.89311,
|
|
||||||
"created_at": 1770692087.560523,
|
|
||||||
"voice": "IKne3meq5aSn9XLyUdCD"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "dc4916a7",
|
"id": "dc4916a7",
|
||||||
"name": "Leon",
|
"name": "Leon",
|
||||||
@@ -82,9 +12,21 @@
|
|||||||
{
|
{
|
||||||
"summary": "Leon, a 63-year-old tow truck driver, called in feeling regretful after pulling a young remote worker's Tesla from a ditch, which reminded him of the computer science acceptance letter he never acted on in 1996 when his girlfriend got pregnant. The conversation became emotional as Leon realized he's the same age his father was when he died, and the host challenged him to stop making excuses and finally pursue the tech career he's been thinking about for decades instead of just \"wondering what could have been.\"",
|
"summary": "Leon, a 63-year-old tow truck driver, called in feeling regretful after pulling a young remote worker's Tesla from a ditch, which reminded him of the computer science acceptance letter he never acted on in 1996 when his girlfriend got pregnant. The conversation became emotional as Leon realized he's the same age his father was when he died, and the host challenged him to stop making excuses and finally pursue the tech career he's been thinking about for decades instead of just \"wondering what could have been.\"",
|
||||||
"timestamp": 1770693549.697355
|
"timestamp": 1770693549.697355
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Leon called back to share that he reached out to UNM about their computer science program and is now deciding between an online bootcamp (which he and his wife Amber can afford without loans) versus a full degree program, ultimately leaning toward the bootcamp since he struggles with self-teaching. He expressed nervousness but appreciation for his daughter holding him accountable, and emotionally shared that buying his reliable used Subaru five years ago changed his life by giving him confidence and reducing stress at his towing job.",
|
||||||
|
"timestamp": 1770951992.186027
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "In this brief clip, the host begins to set up a game with caller Vence, starting to explain the rules before the audio cuts off. There's no substantive conversation or emotional content to summarize.",
|
||||||
|
"timestamp": 1771119313.497329
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Leon called in to play a dating profile game but revealed he's struggling with his coding bootcamp because he's more interested in studying poker strategy than Python. The host encouraged him that at 56, he could pursue becoming a poker pro just as much as anything else, which seemed to resonate with Leon emotionally as he realized poker is what he actually wants to do rather than what he thinks he should do.",
|
||||||
|
"timestamp": 1771119607.065818
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770693549.697355,
|
"last_call": 1771119607.065818,
|
||||||
"created_at": 1770693549.697355,
|
"created_at": 1770693549.697355,
|
||||||
"voice": "CwhRBWXzGAHq8TQ4Fs17"
|
"voice": "CwhRBWXzGAHq8TQ4Fs17"
|
||||||
},
|
},
|
||||||
@@ -144,37 +86,59 @@
|
|||||||
{
|
{
|
||||||
"summary": "Brenda called in to vent about being frustrated with automatic tipping at a diner, where a 20% tip was already added to her bill but the card reader prompted her to add an additional 25-35% while the waitress watched. She expressed feeling pressured and annoyed as an ambulance driver with two kids, struggling with whether to look cheap by reducing the tip, before playing a quick real-or-fake news game with the host.",
|
"summary": "Brenda called in to vent about being frustrated with automatic tipping at a diner, where a 20% tip was already added to her bill but the card reader prompted her to add an additional 25-35% while the waitress watched. She expressed feeling pressured and annoyed as an ambulance driver with two kids, struggling with whether to look cheap by reducing the tip, before playing a quick real-or-fake news game with the host.",
|
||||||
"timestamp": 1770770008.684104
|
"timestamp": 1770770008.684104
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Brenda called in still thinking about whether a waitress remembered her tipping situation from two weeks ago, admitting she cares too much about what strangers think of her. The conversation revealed she's been avoiding dating entirely while working long shifts and dealing with family obligations, acknowledging she obsesses over small social interactions instead of actually putting herself out there romantically.",
|
||||||
|
"timestamp": 1771120062.169228
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770770008.684105,
|
"last_call": 1771120062.169229,
|
||||||
"created_at": 1770770008.684105,
|
"created_at": 1770770008.684105,
|
||||||
"voice": "hpp4J3VqNfWAUOO0d1Us"
|
"voice": "hpp4J3VqNfWAUOO0d1Us"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "49147bd5",
|
"id": "add59d4a",
|
||||||
"name": "Keith",
|
"name": "Rick",
|
||||||
"gender": "male",
|
"gender": "male",
|
||||||
"age": 61,
|
"age": 65,
|
||||||
"job": "south of Silver City",
|
"job": "south of Silver City",
|
||||||
"location": "in unknown",
|
"location": "unknown",
|
||||||
"personality_traits": [],
|
"personality_traits": [],
|
||||||
"call_history": [
|
"call_history": [
|
||||||
{
|
{
|
||||||
"summary": "The caller, Luke, kicked off by sharing a humorous clip of Terrence Howard's Tree of Life Theory being critiqued by Neil deGrasse Tyson, which left Howard visibly hurt, before pivoting to economic woes, blaming overspending and Federal Reserve money printing for devaluing the currency and harming everyday people. He advocated abolishing the Fed, echoing Ron Paul's ideas, to let markets stabilize money, potentially boosting innovation and new industries in rural spots like Silver City despite uncertain local impacts.",
|
"summary": "Rick called in to play \"real news or fake news\" and correctly identified a headline about a geothermal plant sale. He then shared that he's troubled about an elderly bank customer who withdrew $8,000 cash while appearing scared and mentioning his daughter's boyfriend was pressuring him about finances\u2014Rick processed the withdrawal but later learned he should have flagged it as potential elder exploitation, and he's feeling guilty about not intervening.",
|
||||||
"timestamp": 1770524506.3390348
|
"timestamp": 1770771655.536344
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"summary": "Here is a 1-2 sentence summary of the call:\n\nThe caller, who works at a bank, has been reflecting on his tendency to blame the government and economic system for his problems, rather than taking responsibility for his own role. He had an epiphany while eating leftover enchiladas in his minivan, realizing he needs to be more proactive instead of just complaining.",
|
"summary": "Rick, a 65-year-old caller, is asked to evaluate a dating profile for 29-year-old Angela, a \"girl mom\" and MLM skin care seller with strong Christian values. He quickly passes due to the extreme age gap and her intense focus on recruiting for her \"not a pyramid scheme\" business, though he says he'd reconsider if she toned down the sales pitch and religious intensity.",
|
||||||
"timestamp": 1770574890.1296651
|
"timestamp": 1771126337.585641
|
||||||
},
|
|
||||||
{
|
|
||||||
"summary": "Keith called in with an update about a widow who has been showing up weekly at the cemetery where he works nights, but she sits by the maintenance shed rather than visiting her husband's grave, and recently started asking Keith's neighbor personal questions about him. Luke dismissively suggested Keith just talk to the woman and called him a coward for being concerned, leading to some tension before they moved on to playing the real or fake news game.",
|
|
||||||
"timestamp": 1770770394.0436218
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770770394.0436218,
|
"last_call": 1771126337.585642,
|
||||||
"created_at": 1770524506.339036,
|
"created_at": 1770771655.536344,
|
||||||
"voice": "nPczCjzI2devNBz1zQrb"
|
"voice": "TX3LPaxmHKxFdv7VOQHJ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13ff1736",
|
||||||
|
"name": "Jasmine",
|
||||||
|
"gender": "female",
|
||||||
|
"age": 36,
|
||||||
|
"job": "a 61-year-old woman who runs a small bakery in the rural Southwest, finds herself at a crossroads",
|
||||||
|
"location": "unknown",
|
||||||
|
"personality_traits": [],
|
||||||
|
"call_history": [
|
||||||
|
{
|
||||||
|
"summary": "Jasmine called in to defend an earlier caller (Rick) whom she felt the host was too hard on, explaining she's been feeling guilty herself lately. She emotionally revealed that she chose her 1972 Ford Bronco restoration project over her marriage when given an ultimatum, and now regrets sleeping in the guest room with Valentine's Day approaching.",
|
||||||
|
"timestamp": 1770772286.1733272
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Jasmine called to update Luke about her relationship with David after previously discussing their issues over her Ford Bronco obsession. David invited her to watch a SpaceX launch together before Valentine's Day, but she's anxious it will be awkward since they've barely talked in weeks, though Luke convinces her to just enjoy the moment together without forcing conversation.",
|
||||||
|
"timestamp": 1771033676.7729769
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"last_call": 1771033676.7729769,
|
||||||
|
"created_at": 1770772286.1733272,
|
||||||
|
"voice": "pFZP5JQG7iQjIQuC4Bku"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "f21d1346",
|
"id": "f21d1346",
|
||||||
@@ -199,40 +163,60 @@
|
|||||||
"voice": "JBFqnCBsd6RMkjVDRZzb"
|
"voice": "JBFqnCBsd6RMkjVDRZzb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "add59d4a",
|
"id": "d97cb6f9",
|
||||||
"name": "Rick",
|
"name": "Carla",
|
||||||
"gender": "male",
|
"gender": "female",
|
||||||
"age": 65,
|
"age": 26,
|
||||||
"job": "south of Silver City",
|
"job": "is a vet tech",
|
||||||
"location": "unknown",
|
"location": "unknown",
|
||||||
"personality_traits": [],
|
"personality_traits": [],
|
||||||
"call_history": [
|
"call_history": [
|
||||||
{
|
{
|
||||||
"summary": "Rick called in to play \"real news or fake news\" and correctly identified a headline about a geothermal plant sale. He then shared that he's troubled about an elderly bank customer who withdrew $8,000 cash while appearing scared and mentioning his daughter's boyfriend was pressuring him about finances\u2014Rick processed the withdrawal but later learned he should have flagged it as potential elder exploitation, and he's feeling guilty about not intervening.",
|
"summary": "Carla, separated from her husband but not yet divorced, vented about her intrusive in-laws who relentlessly call and dictate her life\u2014from finances and household matters to her clothing choices\u2014while her spineless spouse relays their demands, making her feel trapped in a one-sided war. With her own parents unavailable (father deceased, mother distant), she leans on her bickering but honest sister for support, underscoring her deep frustration and sense of isolation.",
|
||||||
"timestamp": 1770771655.536344
|
"timestamp": 1770522530.8554251
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Carla dismissed celebrity science theories like Terrence Howard's after watching Neil deGrasse Tyson's critique, then marveled at JWST's exoplanet discoveries before sharing her relief at finally cutting off her toxic in-laws amid her ongoing divorce. She expressed deep heartbreak over actor James Ransone's suicide at 46, reflecting on life's fragility, her late father's death, and the need to eliminate family drama, leaving her contemplative and planning a solo desert drive for clarity.",
|
||||||
|
"timestamp": 1770526316.004708
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "In this call, Carla discovered some explicit photos of her ex-husband and his old girlfriend in a box of his old ham radio equipment. She is feeling very uncomfortable about the situation and is seeking advice from the radio host, Luke, on how to best handle and dispose of the photos.",
|
||||||
|
"timestamp": 1770602323.234795
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Carla called with an update about burning the explicit photos of her ex-husband and his old girlfriend, revealing that the girlfriend unexpectedly messaged her on Facebook to \"clear the air\" after apparently hearing about the situation through Carla's previous radio call. When Luke asked about her most embarrassing masturbation material, Carla admitted to using historical romance novels during her failing marriage, explaining she was drawn to the fantasy of men who actually cared and paid attention, unlike her ex-husband who ignored her to play video games.",
|
||||||
|
"timestamp": 1770871317.049056
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Okay, here's a 1-2 sentence summary of the radio call:\n\nThe caller, Carla, was asked to give her honest opinion on a dating profile for a man named Todd. After reviewing the profile, Carla politely declined, explaining that the profile seemed a bit \"try-hard\" for her tastes, and outlined the qualities she would prefer in a potential date, such as a good sense of humor and an adventurous spirit. The host acknowledged that Carla was not interested in dating Todd.",
|
||||||
|
"timestamp": 1771121545.873672
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770771655.536344,
|
"last_call": 1771121545.873673,
|
||||||
"created_at": 1770771655.536344,
|
"created_at": 1770522530.855426,
|
||||||
"voice": "TX3LPaxmHKxFdv7VOQHJ"
|
"voice": "FGY2WhTYpPnrIDTdsKH5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "13ff1736",
|
"id": "7be7317c",
|
||||||
"name": "Jasmine",
|
"name": "Jerome",
|
||||||
"gender": "female",
|
"gender": "male",
|
||||||
"age": 36,
|
"age": 53,
|
||||||
"job": "a 61-year-old woman who runs a small bakery in the rural Southwest, finds herself at a crossroads",
|
"job": "phone",
|
||||||
"location": "unknown",
|
"location": "unknown",
|
||||||
"personality_traits": [],
|
"personality_traits": [],
|
||||||
"call_history": [
|
"call_history": [
|
||||||
{
|
{
|
||||||
"summary": "Jasmine called in to defend an earlier caller (Rick) whom she felt the host was too hard on, explaining she's been feeling guilty herself lately. She emotionally revealed that she chose her 1972 Ford Bronco restoration project over her marriage when given an ultimatum, and now regrets sleeping in the guest room with Valentine's Day approaching.",
|
"summary": "Jerome, a police officer in Texas, called from a DQ parking lot worried about AI writing police reports after his son sent him an article suggesting it might replace him. Through the conversation, he moved from fear about accountability and accuracy in criminal cases to acknowledging that AI handling routine paperwork (like cattle complaints) could free him up to do more meaningful police work in his understaffed county, though he remains uncertain about where this technology will lead.",
|
||||||
"timestamp": 1770772286.1733272
|
"timestamp": 1770692087.560522
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "The caller described a turbulent couple of weeks, mentioning an issue with AI writing police reports, which he suggested was just the beginning of a larger problem. He seemed concerned about the developments and wanted to discuss the topic further with the host.",
|
||||||
|
"timestamp": 1770892192.893108
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770772286.1733272,
|
"last_call": 1770892192.89311,
|
||||||
"created_at": 1770772286.1733272,
|
"created_at": 1770692087.560523,
|
||||||
"voice": "pFZP5JQG7iQjIQuC4Bku"
|
"voice": "IKne3meq5aSn9XLyUdCD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "f383d29b",
|
"id": "f383d29b",
|
||||||
@@ -250,11 +234,63 @@
|
|||||||
{
|
{
|
||||||
"summary": "Here is a 1-2 sentence summary of the call:\n\nThe caller, Megan, is following up on a previous call about her sister Crystal, who lives in Flagstaff and has lost appreciation for the night sky. Megan seems eager to provide an update on the situation with her sister.",
|
"summary": "Here is a 1-2 sentence summary of the call:\n\nThe caller, Megan, is following up on a previous call about her sister Crystal, who lives in Flagstaff and has lost appreciation for the night sky. Megan seems eager to provide an update on the situation with her sister.",
|
||||||
"timestamp": 1770894505.175125
|
"timestamp": 1770894505.175125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "In summary, the caller presented a dating profile for a 63-year-old man named Frank who loves making birdhouses. The host, Megan, gave her honest assessment - she appreciated some aspects of Frank's profile, like his openness about his situation, but had reservations about his intense birdhouse obsession. Megan seemed unsure if they would be a good match, despite the host's attempts to get her to consider dating Frank under different hypothetical circumstances. The conversation focused on Megan's reaction to Frank's profile and her hesitation about pursuing a relationship with him.",
|
||||||
|
"timestamp": 1771122973.966489
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"last_call": 1770894505.175125,
|
"last_call": 1771122973.96649,
|
||||||
"created_at": 1770870641.723117,
|
"created_at": 1770870641.723117,
|
||||||
"voice": "cgSgspJ2msm6clMCkdW9"
|
"voice": "cgSgspJ2msm6clMCkdW9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49147bd5",
|
||||||
|
"name": "Keith",
|
||||||
|
"gender": "male",
|
||||||
|
"age": 61,
|
||||||
|
"job": "south of Silver City",
|
||||||
|
"location": "in unknown",
|
||||||
|
"personality_traits": [],
|
||||||
|
"call_history": [
|
||||||
|
{
|
||||||
|
"summary": "The caller, Luke, kicked off by sharing a humorous clip of Terrence Howard's Tree of Life Theory being critiqued by Neil deGrasse Tyson, which left Howard visibly hurt, before pivoting to economic woes, blaming overspending and Federal Reserve money printing for devaluing the currency and harming everyday people. He advocated abolishing the Fed, echoing Ron Paul's ideas, to let markets stabilize money, potentially boosting innovation and new industries in rural spots like Silver City despite uncertain local impacts.",
|
||||||
|
"timestamp": 1770524506.3390348
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Here is a 1-2 sentence summary of the call:\n\nThe caller, who works at a bank, has been reflecting on his tendency to blame the government and economic system for his problems, rather than taking responsibility for his own role. He had an epiphany while eating leftover enchiladas in his minivan, realizing he needs to be more proactive instead of just complaining.",
|
||||||
|
"timestamp": 1770574890.1296651
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Keith called in with an update about a widow who has been showing up weekly at the cemetery where he works nights, but she sits by the maintenance shed rather than visiting her husband's grave, and recently started asking Keith's neighbor personal questions about him. Luke dismissively suggested Keith just talk to the woman and called him a coward for being concerned, leading to some tension before they moved on to playing the real or fake news game.",
|
||||||
|
"timestamp": 1770770394.0436218
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Keith called back to update the host about a widow he befriended at the cemetery where he works, revealing she's been seeking him out during his shifts, bringing him coffee, and has now invited him to her apartment\u2014which he's conflicted about because his marriage to Teresa has become cold and distant, though he's scared to address it. The conversation shifted from the widow situation to Keith admitting he needs to have hard conversations with his wife about their deteriorating relationship, and he got emotional reflecting on how he and Teresa \"stopped being on the same team\" and how terrifying it would be to split up after being together for over half his life.",
|
||||||
|
"timestamp": 1770950476.527814
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"last_call": 1770950476.527814,
|
||||||
|
"created_at": 1770524506.339036,
|
||||||
|
"voice": "nPczCjzI2devNBz1zQrb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0d244eeb",
|
||||||
|
"name": "Gus",
|
||||||
|
"gender": "male",
|
||||||
|
"age": 33,
|
||||||
|
"job": "",
|
||||||
|
"location": "in unknown",
|
||||||
|
"personality_traits": [],
|
||||||
|
"voice": "Alex",
|
||||||
|
"call_history": [
|
||||||
|
{
|
||||||
|
"summary": "Gus called because his ex Melissa showed up at his pawn shop job with flowers wanting to reconcile, and his current girlfriend Sara saw it through the window and now won't talk to him. Despite the host's dismissive advice (including sarcastically suggesting he regift the same flowers), Gus insisted he wants to be with Sara and acknowledged he should have shut down his ex immediately instead of freezing up, though he defended that Sara's reaction to seeing this wasn't unreasonable jealousy.",
|
||||||
|
"timestamp": 1770951226.534601
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"last_call": 1770951226.534601,
|
||||||
|
"created_at": 1770951226.534601
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -376,9 +376,19 @@ section h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Soundboard */
|
/* Soundboard */
|
||||||
|
.sounds-section {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.sounds-section {
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.soundboard {
|
.soundboard {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(6, 1fr);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,10 +143,10 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="channel-row">
|
<div class="channel-row">
|
||||||
<label>Caller Ch <input type="number" id="caller-channel" value="1" min="1" max="16" class="channel-input"></label>
|
<label>Caller Ch <input type="number" id="caller-channel" value="3" min="1" max="16" class="channel-input"></label>
|
||||||
<label>Live Ch <input type="number" id="live-caller-channel" value="9" min="1" max="16" class="channel-input"></label>
|
<label>Live Ch <input type="number" id="live-caller-channel" value="9" min="1" max="16" class="channel-input"></label>
|
||||||
<label>Music Ch <input type="number" id="music-channel" value="2" min="1" max="16" class="channel-input"></label>
|
<label>Music Ch <input type="number" id="music-channel" value="5" min="1" max="16" class="channel-input"></label>
|
||||||
<label>SFX Ch <input type="number" id="sfx-channel" value="3" min="1" max="16" class="channel-input"></label>
|
<label>SFX Ch <input type="number" id="sfx-channel" value="7" min="1" max="16" class="channel-input"></label>
|
||||||
<label>Ad Ch <input type="number" id="ad-channel" value="11" min="1" max="16" class="channel-input"></label>
|
<label>Ad Ch <input type="number" id="ad-channel" value="11" min="1" max="16" class="channel-input"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Usage:
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -128,7 +129,7 @@ def gather_youtube(include_comments=False):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["yt-dlp", "--dump-json", "--flat-playlist",
|
[os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv", "bin", "yt-dlp"), "--dump-json", "--flat-playlist",
|
||||||
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
|
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
|
||||||
capture_output=True, text=True, timeout=60
|
capture_output=True, text=True, timeout=60
|
||||||
)
|
)
|
||||||
@@ -159,7 +160,7 @@ def gather_youtube(include_comments=False):
|
|||||||
|
|
||||||
for vid in video_ids:
|
for vid in video_ids:
|
||||||
try:
|
try:
|
||||||
cmd = ["yt-dlp", "--dump-json", "--no-download", f"https://www.youtube.com/watch?v={vid}"]
|
cmd = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv", "bin", "yt-dlp"), "--dump-json", "--no-download", f"https://www.youtube.com/watch?v={vid}"]
|
||||||
if include_comments:
|
if include_comments:
|
||||||
cmd.insert(2, "--write-comments")
|
cmd.insert(2, "--write-comments")
|
||||||
vr = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
|
vr = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
|
||||||
@@ -203,7 +204,7 @@ def gather_youtube(include_comments=False):
|
|||||||
if videos:
|
if videos:
|
||||||
try:
|
try:
|
||||||
vr = subprocess.run(
|
vr = subprocess.run(
|
||||||
["yt-dlp", "--dump-json", "--no-download", "--playlist-items", "1",
|
[os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv", "bin", "yt-dlp"), "--dump-json", "--no-download", "--playlist-items", "1",
|
||||||
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
|
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
|
||||||
capture_output=True, text=True, timeout=30
|
capture_output=True, text=True, timeout=30
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ def compute_rms(audio: np.ndarray, window_samples: int) -> np.ndarray:
|
|||||||
|
|
||||||
|
|
||||||
def remove_gaps(stems: dict[str, np.ndarray], sr: int,
|
def remove_gaps(stems: dict[str, np.ndarray], sr: int,
|
||||||
threshold_s: float = 2.0, max_gap_s: float = 8.0,
|
threshold_s: float = 2.0, max_gap_s: float = 15.0,
|
||||||
crossfade_ms: float = 30, pad_s: float = 0.5) -> dict[str, np.ndarray]:
|
crossfade_ms: float = 30, pad_s: float = 0.5) -> dict[str, np.ndarray]:
|
||||||
window_ms = 50
|
window_ms = 50
|
||||||
window_samples = int(sr * window_ms / 1000)
|
window_samples = int(sr * window_ms / 1000)
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import base64
|
from datetime import datetime, timezone
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
@@ -61,6 +62,19 @@ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
|||||||
|
|
||||||
WHISPER_MODEL = "base" # Options: tiny, base, small, medium, large
|
WHISPER_MODEL = "base" # Options: tiny, base, small, medium, large
|
||||||
|
|
||||||
|
# Postiz (social media posting)
|
||||||
|
POSTIZ_URL = "https://social.lukeattheroost.com"
|
||||||
|
POSTIZ_JWT_SECRET = "9d499bab97b303506af6ae18b29a60e6b5a0b1049177f533232ad14dd9729814"
|
||||||
|
POSTIZ_USER_ID = "00c14319-9eac-42c3-a467-68d3c1634fe1"
|
||||||
|
POSTIZ_INTEGRATIONS = {
|
||||||
|
"facebook": {"id": "cmll9hwqj0001mt6xnas2f17w"},
|
||||||
|
"instagram": {"id": "cmlljn8920001pk6qqzutqwik"},
|
||||||
|
"discord": {"id": "cmllkprk90001uc6v6fwd5y9p", "channel": "1471386314447519754"},
|
||||||
|
"bluesky": {"id": "cmlk29h780001p76qa7sstp5h"},
|
||||||
|
"mastodon": {"id": "cmlk2r3mf0001le6vx9ey0k5a"},
|
||||||
|
"nostr": {"id": "cmlll3y78000cuc6vh8dcpl2w"},
|
||||||
|
}
|
||||||
|
|
||||||
# NAS Configuration for chapters upload
|
# NAS Configuration for chapters upload
|
||||||
# BunnyCDN Storage
|
# BunnyCDN Storage
|
||||||
BUNNY_STORAGE_ZONE = "lukeattheroost"
|
BUNNY_STORAGE_ZONE = "lukeattheroost"
|
||||||
@@ -276,10 +290,23 @@ Respond with ONLY valid JSON, no markdown or explanation."""
|
|||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
def create_episode(audio_path: str, metadata: dict, episode_number: int) -> dict:
|
CLOUDFLARE_UPLOAD_LIMIT = 100 * 1024 * 1024 # 100 MB
|
||||||
"""Create episode on Castopod using curl (handles large file uploads better)."""
|
|
||||||
print("[3/5] Creating episode on Castopod...")
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_episode(audio_path: str, metadata: dict, episode_number: int, duration: int = 0) -> dict:
|
||||||
|
"""Create episode on Castopod. Bypasses Cloudflare for large files."""
|
||||||
|
file_size = os.path.getsize(audio_path)
|
||||||
|
|
||||||
|
if file_size > CLOUDFLARE_UPLOAD_LIMIT:
|
||||||
|
print(f"[3/5] Creating episode on Castopod (direct, {file_size / 1024 / 1024:.0f} MB > 100 MB limit)...")
|
||||||
|
return _create_episode_direct(audio_path, metadata, episode_number, file_size, duration)
|
||||||
|
|
||||||
|
print("[3/5] Creating episode on Castopod...")
|
||||||
|
return _create_episode_api(audio_path, metadata, episode_number)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_episode_api(audio_path: str, metadata: dict, episode_number: int) -> dict:
|
||||||
|
"""Create episode via Castopod REST API (through Cloudflare)."""
|
||||||
credentials = base64.b64encode(
|
credentials = base64.b64encode(
|
||||||
f"{CASTOPOD_USERNAME}:{CASTOPOD_PASSWORD}".encode()
|
f"{CASTOPOD_USERNAME}:{CASTOPOD_PASSWORD}".encode()
|
||||||
).decode()
|
).decode()
|
||||||
@@ -301,7 +328,7 @@ def create_episode(audio_path: str, metadata: dict, episode_number: int) -> dict
|
|||||||
"-F", f"episode_number={episode_number}",
|
"-F", f"episode_number={episode_number}",
|
||||||
]
|
]
|
||||||
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=900)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"Error uploading: {result.stderr}")
|
print(f"Error uploading: {result.stderr}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -322,6 +349,107 @@ def create_episode(audio_path: str, metadata: dict, episode_number: int) -> dict
|
|||||||
return episode
|
return episode
|
||||||
|
|
||||||
|
|
||||||
|
def _create_episode_direct(audio_path: str, metadata: dict, episode_number: int,
|
||||||
|
file_size: int, duration: int) -> dict:
|
||||||
|
"""Create episode by uploading directly to NAS and inserting into DB."""
|
||||||
|
import time as _time
|
||||||
|
slug = re.sub(r'[^a-z0-9]+', '-', metadata["title"].lower()).strip('-')
|
||||||
|
timestamp = int(_time.time())
|
||||||
|
rand_hex = os.urandom(10).hex()
|
||||||
|
filename = f"{timestamp}_{rand_hex}.mp3"
|
||||||
|
file_key = f"podcasts/{PODCAST_HANDLE}/{filename}"
|
||||||
|
nas_tmp = f"/share/CACHEDEV1_DATA/tmp/{filename}"
|
||||||
|
guid = f"{CASTOPOD_URL}/@{PODCAST_HANDLE}/episodes/{slug}"
|
||||||
|
desc_md = metadata["description"]
|
||||||
|
desc_html = f"<p>{desc_md}</p>"
|
||||||
|
duration_json = json.dumps({"playtime_seconds": duration, "avdataoffset": 85})
|
||||||
|
|
||||||
|
# SCP audio to NAS
|
||||||
|
print(" Uploading audio to NAS...")
|
||||||
|
scp_cmd = ["scp", "-P", str(NAS_SSH_PORT), audio_path, f"{NAS_USER}@{NAS_HOST}:{nas_tmp}"]
|
||||||
|
result = subprocess.run(scp_cmd, capture_output=True, text=True, timeout=600)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error: SCP failed: {result.stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Docker cp into Castopod container
|
||||||
|
print(" Copying into Castopod container...")
|
||||||
|
media_path = f"/var/www/castopod/public/media/{file_key}"
|
||||||
|
cp_cmd = f'{DOCKER_PATH} cp {nas_tmp} {CASTOPOD_CONTAINER}:{media_path}'
|
||||||
|
success, output = run_ssh_command(cp_cmd, timeout=120)
|
||||||
|
if not success:
|
||||||
|
print(f"Error: docker cp failed: {output}")
|
||||||
|
sys.exit(1)
|
||||||
|
run_ssh_command(f'{DOCKER_PATH} exec {CASTOPOD_CONTAINER} chown www-data:www-data {media_path}')
|
||||||
|
run_ssh_command(f"rm -f {nas_tmp}")
|
||||||
|
|
||||||
|
# Build SQL and transfer via base64 to avoid shell escaping issues
|
||||||
|
print(" Inserting media and episode records...")
|
||||||
|
|
||||||
|
def _mysql_escape(s: str) -> str:
|
||||||
|
"""Escape a string for MySQL single-quoted literals."""
|
||||||
|
return s.replace("\\", "\\\\").replace("'", "\\'")
|
||||||
|
|
||||||
|
title_esc = _mysql_escape(metadata["title"])
|
||||||
|
desc_md_esc = _mysql_escape(desc_md)
|
||||||
|
desc_html_esc = _mysql_escape(desc_html)
|
||||||
|
duration_json_esc = _mysql_escape(duration_json)
|
||||||
|
|
||||||
|
sql = (
|
||||||
|
f"INSERT INTO cp_media (file_key, file_size, file_mimetype, file_metadata, type, "
|
||||||
|
f"uploaded_by, updated_by, uploaded_at, updated_at) VALUES "
|
||||||
|
f"('{file_key}', {file_size}, 'audio/mpeg', '{duration_json_esc}', 'audio', 1, 1, NOW(), NOW());\n"
|
||||||
|
f"SET @audio_id = LAST_INSERT_ID();\n"
|
||||||
|
f"INSERT INTO cp_episodes (podcast_id, guid, title, slug, audio_id, "
|
||||||
|
f"description_markdown, description_html, parental_advisory, number, type, "
|
||||||
|
f"is_blocked, is_published_on_hubs, is_premium, created_by, updated_by, "
|
||||||
|
f"published_at, created_at, updated_at) VALUES "
|
||||||
|
f"(1, '{guid}', '{title_esc}', '{slug}', @audio_id, "
|
||||||
|
f"'{desc_md_esc}', '{desc_html_esc}', 'explicit', {episode_number}, 'full', "
|
||||||
|
f"0, 0, 0, 1, 1, NOW(), NOW(), NOW());\n"
|
||||||
|
f"SELECT LAST_INSERT_ID();\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write SQL to local temp file, SCP to NAS, docker cp into MariaDB
|
||||||
|
local_sql_path = "/tmp/_castopod_insert.sql"
|
||||||
|
nas_sql_path = "/share/CACHEDEV1_DATA/tmp/_castopod_insert.sql"
|
||||||
|
with open(local_sql_path, "w") as f:
|
||||||
|
f.write(sql)
|
||||||
|
scp_sql = ["scp", "-P", str(NAS_SSH_PORT), local_sql_path, f"{NAS_USER}@{NAS_HOST}:{nas_sql_path}"]
|
||||||
|
result = subprocess.run(scp_sql, capture_output=True, text=True, timeout=30)
|
||||||
|
os.remove(local_sql_path)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error: failed to SCP SQL file: {result.stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Copy SQL into MariaDB container and execute
|
||||||
|
run_ssh_command(f'{DOCKER_PATH} cp {nas_sql_path} {MARIADB_CONTAINER}:/tmp/_insert.sql')
|
||||||
|
exec_cmd = f'{DOCKER_PATH} exec {MARIADB_CONTAINER} sh -c "mysql -u {DB_USER} -p{DB_PASS} {DB_NAME} -N < /tmp/_insert.sql"'
|
||||||
|
success, output = run_ssh_command(exec_cmd, timeout=30)
|
||||||
|
run_ssh_command(f'rm -f {nas_sql_path}')
|
||||||
|
run_ssh_command(f'{DOCKER_PATH} exec {MARIADB_CONTAINER} rm -f /tmp/_insert.sql')
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print(f"Error: DB insert failed: {output}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
episode_id = int(output.strip().split('\n')[-1])
|
||||||
|
# Get the audio media ID for CDN upload
|
||||||
|
audio_id_cmd = f'{DOCKER_PATH} exec {MARIADB_CONTAINER} mysql -u {DB_USER} -p{DB_PASS} {DB_NAME} -N -e "SELECT audio_id FROM cp_episodes WHERE id = {episode_id};"'
|
||||||
|
success, audio_id_str = run_ssh_command(audio_id_cmd)
|
||||||
|
audio_id = int(audio_id_str.strip()) if success else None
|
||||||
|
if audio_id:
|
||||||
|
print(f" Audio media ID: {audio_id}")
|
||||||
|
|
||||||
|
# Clear cache
|
||||||
|
run_ssh_command(f'{DOCKER_PATH} exec {CASTOPOD_CONTAINER} php spark cache:clear')
|
||||||
|
|
||||||
|
print(f" Created episode ID: {episode_id}")
|
||||||
|
print(f" Slug: {slug}")
|
||||||
|
|
||||||
|
return {"id": episode_id, "slug": slug}
|
||||||
|
|
||||||
|
|
||||||
def publish_episode(episode_id: int) -> dict:
|
def publish_episode(episode_id: int) -> dict:
|
||||||
"""Publish the episode."""
|
"""Publish the episode."""
|
||||||
print("[4/5] Publishing episode...")
|
print("[4/5] Publishing episode...")
|
||||||
@@ -451,7 +579,7 @@ def upload_to_bunny(local_path: str, remote_path: str, content_type: str = None)
|
|||||||
resp = requests.put(url, data=f, headers={
|
resp = requests.put(url, data=f, headers={
|
||||||
"AccessKey": BUNNY_STORAGE_KEY,
|
"AccessKey": BUNNY_STORAGE_KEY,
|
||||||
"Content-Type": content_type,
|
"Content-Type": content_type,
|
||||||
})
|
}, timeout=600)
|
||||||
if resp.status_code == 201:
|
if resp.status_code == 201:
|
||||||
return True
|
return True
|
||||||
print(f" Warning: BunnyCDN upload failed ({resp.status_code}): {resp.text[:200]}")
|
print(f" Warning: BunnyCDN upload failed ({resp.status_code}): {resp.text[:200]}")
|
||||||
@@ -461,7 +589,7 @@ def upload_to_bunny(local_path: str, remote_path: str, content_type: str = None)
|
|||||||
def download_from_castopod(file_key: str, local_path: str) -> bool:
|
def download_from_castopod(file_key: str, local_path: str) -> bool:
|
||||||
"""Download a file from Castopod's container storage to local filesystem."""
|
"""Download a file from Castopod's container storage to local filesystem."""
|
||||||
remote_filename = Path(file_key).name
|
remote_filename = Path(file_key).name
|
||||||
remote_tmp = f"/tmp/castopod_{remote_filename}"
|
remote_tmp = f"/share/CACHEDEV1_DATA/tmp/castopod_{remote_filename}"
|
||||||
cp_cmd = f'{DOCKER_PATH} cp {CASTOPOD_CONTAINER}:/var/www/castopod/public/media/{file_key} {remote_tmp}'
|
cp_cmd = f'{DOCKER_PATH} cp {CASTOPOD_CONTAINER}:/var/www/castopod/public/media/{file_key} {remote_tmp}'
|
||||||
success, _ = run_ssh_command(cp_cmd, timeout=120)
|
success, _ = run_ssh_command(cp_cmd, timeout=120)
|
||||||
if not success:
|
if not success:
|
||||||
@@ -545,6 +673,174 @@ def add_episode_to_sitemap(slug: str):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def generate_social_image(episode_number: int, description: str, output_path: str) -> str:
|
||||||
|
"""Generate a social media image with cover art, episode number, and description."""
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
COVER_ART = Path(__file__).parent / "website" / "images" / "cover.png"
|
||||||
|
SIZE = 1080
|
||||||
|
|
||||||
|
img = Image.open(COVER_ART).convert("RGBA")
|
||||||
|
img = img.resize((SIZE, SIZE), Image.LANCZOS)
|
||||||
|
|
||||||
|
# Dark gradient overlay on the bottom ~45%
|
||||||
|
gradient = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
||||||
|
draw_grad = ImageDraw.Draw(gradient)
|
||||||
|
gradient_start = int(SIZE * 0.50)
|
||||||
|
for y in range(gradient_start, SIZE):
|
||||||
|
progress = (y - gradient_start) / (SIZE - gradient_start)
|
||||||
|
alpha = int(210 * progress)
|
||||||
|
draw_grad.line([(0, y), (SIZE, y)], fill=(0, 0, 0, alpha))
|
||||||
|
|
||||||
|
img = Image.alpha_composite(img, gradient)
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
try:
|
||||||
|
font_episode = ImageFont.truetype("/Library/Fonts/Montserrat-ExtraBold.ttf", 64)
|
||||||
|
font_desc = ImageFont.truetype("/Library/Fonts/Montserrat-Medium.ttf", 36)
|
||||||
|
font_url = ImageFont.truetype("/Library/Fonts/Montserrat-SemiBold.ttf", 28)
|
||||||
|
except OSError:
|
||||||
|
font_episode = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", 64)
|
||||||
|
font_desc = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", 36)
|
||||||
|
font_url = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", 28)
|
||||||
|
|
||||||
|
margin = 60
|
||||||
|
max_width = SIZE - margin * 2
|
||||||
|
|
||||||
|
# Episode number
|
||||||
|
ep_text = f"EPISODE {episode_number}"
|
||||||
|
draw.text((margin, SIZE - 300), ep_text, font=font_episode, fill=(255, 200, 80))
|
||||||
|
|
||||||
|
# Description — word-wrap to fit
|
||||||
|
wrapped = textwrap.fill(description, width=45)
|
||||||
|
lines = wrapped.split("\n")[:4] # max 4 lines
|
||||||
|
if len(wrapped.split("\n")) > 4:
|
||||||
|
lines[-1] = lines[-1][:lines[-1].rfind(" ")] + "..."
|
||||||
|
desc_text = "\n".join(lines)
|
||||||
|
draw.text((margin, SIZE - 220), desc_text, font=font_desc, fill=(255, 255, 255, 230),
|
||||||
|
spacing=8)
|
||||||
|
|
||||||
|
# Website URL — bottom right
|
||||||
|
url_text = "lukeattheroost.com"
|
||||||
|
bbox = draw.textbbox((0, 0), url_text, font=font_url)
|
||||||
|
url_width = bbox[2] - bbox[0]
|
||||||
|
draw.text((SIZE - margin - url_width, SIZE - 50), url_text, font=font_url,
|
||||||
|
fill=(255, 200, 80, 200))
|
||||||
|
|
||||||
|
img = img.convert("RGB")
|
||||||
|
img.save(output_path, "JPEG", quality=92)
|
||||||
|
print(f" Social image saved: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
|
||||||
|
def _get_postiz_token():
|
||||||
|
"""Generate a JWT token for Postiz API authentication."""
|
||||||
|
import jwt
|
||||||
|
return jwt.encode(
|
||||||
|
{"id": POSTIZ_USER_ID, "email": "luke@macneilmediagroup.com",
|
||||||
|
"providerName": "LOCAL", "activated": True, "isSuperAdmin": False},
|
||||||
|
POSTIZ_JWT_SECRET, algorithm="HS256"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_image_to_postiz(image_path: str) -> dict | None:
|
||||||
|
"""Upload an image to Postiz and return the media object."""
|
||||||
|
token = _get_postiz_token()
|
||||||
|
try:
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
resp = requests.post(
|
||||||
|
f"{POSTIZ_URL}/api/media/upload-simple",
|
||||||
|
headers={"auth": token},
|
||||||
|
files={"file": ("social.jpg", f, "image/jpeg")},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
if resp.status_code in (200, 201):
|
||||||
|
media = resp.json()
|
||||||
|
print(f" Uploaded image to Postiz (id: {media.get('id', 'unknown')})")
|
||||||
|
return media
|
||||||
|
else:
|
||||||
|
print(f" Warning: Postiz image upload returned {resp.status_code}: {resp.text[:200]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Warning: Postiz image upload failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def post_to_social(metadata: dict, episode_slug: str, image_path: str = None):
|
||||||
|
"""Post episode announcement to all connected social channels via Postiz."""
|
||||||
|
print("[5.5/5] Posting to social media...")
|
||||||
|
|
||||||
|
token = _get_postiz_token()
|
||||||
|
|
||||||
|
# Upload image if provided
|
||||||
|
image_ids = []
|
||||||
|
if image_path:
|
||||||
|
media = upload_image_to_postiz(image_path)
|
||||||
|
if media and media.get("id"):
|
||||||
|
image_ids = [{"id": media["id"], "path": media.get("path", "")}]
|
||||||
|
|
||||||
|
episode_url = f"https://lukeattheroost.com/episode.html?slug={episode_slug}"
|
||||||
|
base_content = f"{metadata['title']}\n\n{metadata['description']}\n\n{episode_url}"
|
||||||
|
|
||||||
|
hashtags = "#podcast #LukeAtTheRoost #talkradio #callinshow #newepisode"
|
||||||
|
hashtag_platforms = {"instagram", "facebook", "bluesky", "mastodon", "nostr"}
|
||||||
|
|
||||||
|
# Platform-specific content length limits
|
||||||
|
PLATFORM_MAX_LENGTH = {"bluesky": 300}
|
||||||
|
|
||||||
|
# Post to each platform individually so one failure doesn't block others
|
||||||
|
posted = 0
|
||||||
|
for platform, intg_config in POSTIZ_INTEGRATIONS.items():
|
||||||
|
content = base_content
|
||||||
|
if platform in hashtag_platforms:
|
||||||
|
content += f"\n\n{hashtags}"
|
||||||
|
|
||||||
|
# Truncate for platforms with short limits
|
||||||
|
max_len = PLATFORM_MAX_LENGTH.get(platform)
|
||||||
|
if max_len and len(content) > max_len:
|
||||||
|
# Keep title + URL, truncate description
|
||||||
|
short = f"{metadata['title']}\n\n{episode_url}"
|
||||||
|
if platform in hashtag_platforms:
|
||||||
|
short += f"\n\n{hashtags}"
|
||||||
|
content = short[:max_len]
|
||||||
|
|
||||||
|
settings = {"post_type": "post"}
|
||||||
|
if "channel" in intg_config:
|
||||||
|
settings["channel"] = intg_config["channel"]
|
||||||
|
|
||||||
|
post = {
|
||||||
|
"integration": {"id": intg_config["id"]},
|
||||||
|
"value": [{"content": content, "image": image_ids}],
|
||||||
|
"settings": settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"type": "now",
|
||||||
|
"shortLink": False,
|
||||||
|
"date": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
||||||
|
"tags": [],
|
||||||
|
"posts": [post],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(
|
||||||
|
f"{POSTIZ_URL}/api/posts",
|
||||||
|
headers={"auth": token, "Content-Type": "application/json"},
|
||||||
|
json=payload,
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
if resp.status_code in (200, 201):
|
||||||
|
posted += 1
|
||||||
|
print(f" Posted to {platform}")
|
||||||
|
else:
|
||||||
|
print(f" Warning: {platform} failed ({resp.status_code}): {resp.text[:150]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Warning: {platform} failed: {e}")
|
||||||
|
|
||||||
|
print(f" Posted to {posted}/{len(POSTIZ_INTEGRATIONS)} channels")
|
||||||
|
|
||||||
|
|
||||||
def get_next_episode_number() -> int:
|
def get_next_episode_number() -> int:
|
||||||
"""Get the next episode number from Castopod."""
|
"""Get the next episode number from Castopod."""
|
||||||
headers = get_auth_header()
|
headers = get_auth_header()
|
||||||
@@ -648,30 +944,37 @@ def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Step 3: Create episode
|
# Step 3: Create episode
|
||||||
episode = create_episode(str(audio_path), metadata, episode_number)
|
direct_upload = os.path.getsize(str(audio_path)) > CLOUDFLARE_UPLOAD_LIMIT
|
||||||
|
episode = create_episode(str(audio_path), metadata, episode_number, duration=transcript["duration"])
|
||||||
|
|
||||||
# Step 3.5: Upload to BunnyCDN
|
# Step 3.5: Upload to BunnyCDN
|
||||||
print("[3.5/5] Uploading to BunnyCDN...")
|
print("[3.5/5] Uploading to BunnyCDN...")
|
||||||
uploaded_keys = set()
|
uploaded_keys = set()
|
||||||
|
|
||||||
# Audio: download Castopod's copy (ensures byte-exact match with RSS metadata)
|
# Audio: query file_key from DB, then upload to CDN
|
||||||
ep_id = episode["id"]
|
ep_id = episode["id"]
|
||||||
audio_media_cmd = f'{DOCKER_PATH} exec {MARIADB_CONTAINER} mysql -u {DB_USER} -p{DB_PASS} {DB_NAME} -N -e "SELECT m.file_key FROM cp_media m JOIN cp_episodes e ON e.audio_id = m.id WHERE e.id = {ep_id};"'
|
audio_media_cmd = f'{DOCKER_PATH} exec {MARIADB_CONTAINER} mysql -u {DB_USER} -p{DB_PASS} {DB_NAME} -N -e "SELECT m.file_key FROM cp_media m JOIN cp_episodes e ON e.audio_id = m.id WHERE e.id = {ep_id};"'
|
||||||
success, audio_file_key = run_ssh_command(audio_media_cmd)
|
success, audio_file_key = run_ssh_command(audio_media_cmd)
|
||||||
if success and audio_file_key:
|
if success and audio_file_key:
|
||||||
audio_file_key = audio_file_key.strip()
|
audio_file_key = audio_file_key.strip()
|
||||||
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp:
|
if direct_upload:
|
||||||
tmp_audio = tmp.name
|
# Direct upload: we have the original file locally, upload straight to CDN
|
||||||
try:
|
print(f" Uploading audio to BunnyCDN")
|
||||||
print(f" Downloading from Castopod: {audio_file_key}")
|
upload_to_bunny(str(audio_path), f"media/{audio_file_key}", "audio/mpeg")
|
||||||
if download_from_castopod(audio_file_key, tmp_audio):
|
else:
|
||||||
print(f" Uploading audio to BunnyCDN")
|
# API upload: download Castopod's copy (ensures byte-exact match with RSS metadata)
|
||||||
upload_to_bunny(tmp_audio, f"media/{audio_file_key}", "audio/mpeg")
|
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp:
|
||||||
else:
|
tmp_audio = tmp.name
|
||||||
print(f" Castopod download failed, uploading original file")
|
try:
|
||||||
upload_to_bunny(str(audio_path), f"media/{audio_file_key}", "audio/mpeg")
|
print(f" Downloading from Castopod: {audio_file_key}")
|
||||||
finally:
|
if download_from_castopod(audio_file_key, tmp_audio):
|
||||||
Path(tmp_audio).unlink(missing_ok=True)
|
print(f" Uploading audio to BunnyCDN")
|
||||||
|
upload_to_bunny(tmp_audio, f"media/{audio_file_key}", "audio/mpeg")
|
||||||
|
else:
|
||||||
|
print(f" Castopod download failed, uploading original file")
|
||||||
|
upload_to_bunny(str(audio_path), f"media/{audio_file_key}", "audio/mpeg")
|
||||||
|
finally:
|
||||||
|
Path(tmp_audio).unlink(missing_ok=True)
|
||||||
uploaded_keys.add(audio_file_key)
|
uploaded_keys.add(audio_file_key)
|
||||||
else:
|
else:
|
||||||
print(f" Error: Could not determine audio file_key from Castopod DB")
|
print(f" Error: Could not determine audio file_key from Castopod DB")
|
||||||
@@ -688,7 +991,6 @@ def main():
|
|||||||
upload_to_bunny(str(transcript_path), f"transcripts/{episode['slug']}.txt", "text/plain")
|
upload_to_bunny(str(transcript_path), f"transcripts/{episode['slug']}.txt", "text/plain")
|
||||||
|
|
||||||
# Copy transcript to website dir for Cloudflare Pages
|
# Copy transcript to website dir for Cloudflare Pages
|
||||||
import shutil
|
|
||||||
website_transcript_dir = Path(__file__).parent / "website" / "transcripts"
|
website_transcript_dir = Path(__file__).parent / "website" / "transcripts"
|
||||||
website_transcript_dir.mkdir(exist_ok=True)
|
website_transcript_dir.mkdir(exist_ok=True)
|
||||||
website_transcript_path = website_transcript_dir / f"{episode['slug']}.txt"
|
website_transcript_path = website_transcript_dir / f"{episode['slug']}.txt"
|
||||||
@@ -698,8 +1000,16 @@ def main():
|
|||||||
# Add to sitemap
|
# Add to sitemap
|
||||||
add_episode_to_sitemap(episode["slug"])
|
add_episode_to_sitemap(episode["slug"])
|
||||||
|
|
||||||
# Step 4: Publish
|
# Step 4: Publish via API (triggers RSS rebuild, federation, etc.)
|
||||||
episode = publish_episode(episode["id"])
|
try:
|
||||||
|
published = publish_episode(episode["id"])
|
||||||
|
if "slug" in published:
|
||||||
|
episode = published
|
||||||
|
except SystemExit:
|
||||||
|
if direct_upload:
|
||||||
|
print(" Warning: Publish API failed, but episode is in DB with published_at set")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Step 4.5: Upload chapters via SSH
|
# Step 4.5: Upload chapters via SSH
|
||||||
chapters_uploaded = upload_chapters_to_castopod(
|
chapters_uploaded = upload_chapters_to_castopod(
|
||||||
@@ -712,8 +1022,26 @@ def main():
|
|||||||
print(" Syncing episode media to CDN...")
|
print(" Syncing episode media to CDN...")
|
||||||
sync_episode_media_to_bunny(episode["id"], uploaded_keys)
|
sync_episode_media_to_bunny(episode["id"], uploaded_keys)
|
||||||
|
|
||||||
# Step 5: Summary
|
# Step 5: Deploy website (transcript + sitemap must be live before social links go out)
|
||||||
print("\n[5/5] Done!")
|
print("[5/5] Deploying website...")
|
||||||
|
project_dir = Path(__file__).parent
|
||||||
|
deploy_result = subprocess.run(
|
||||||
|
["npx", "wrangler", "pages", "deploy", "website/",
|
||||||
|
"--project-name=lukeattheroost", "--branch=main", "--commit-dirty=true"],
|
||||||
|
capture_output=True, text=True, cwd=project_dir, timeout=120
|
||||||
|
)
|
||||||
|
if deploy_result.returncode == 0:
|
||||||
|
print(" Website deployed")
|
||||||
|
else:
|
||||||
|
print(f" Warning: Website deploy failed: {deploy_result.stderr[:200]}")
|
||||||
|
|
||||||
|
# Step 5.5: Generate social image and post
|
||||||
|
social_image_path = str(audio_path.with_suffix(".social.jpg"))
|
||||||
|
generate_social_image(episode_number, metadata["description"], social_image_path)
|
||||||
|
post_to_social(metadata, episode["slug"], social_image_path)
|
||||||
|
|
||||||
|
# Step 6: Summary
|
||||||
|
print("\n[6/6] Done!")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
print(f"Episode URL: {CASTOPOD_URL}/@{PODCAST_HANDLE}/episodes/{episode['slug']}")
|
print(f"Episode URL: {CASTOPOD_URL}/@{PODCAST_HANDLE}/episodes/{episode['slug']}")
|
||||||
print(f"RSS Feed: {CASTOPOD_URL}/@{PODCAST_HANDLE}/feed.xml")
|
print(f"RSS Feed: {CASTOPOD_URL}/@{PODCAST_HANDLE}/feed.xml")
|
||||||
|
|||||||
@@ -181,22 +181,22 @@ a:hover {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subscribe buttons */
|
/* Subscribe buttons — primary listen platforms */
|
||||||
.subscribe-row {
|
.subscribe-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.75rem;
|
gap: 0.6rem;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subscribe-btn {
|
.subscribe-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.45rem;
|
||||||
padding: 0.6rem 1.25rem;
|
padding: 0.55rem 1.1rem;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
transition: opacity 0.2s, transform 0.2s;
|
transition: opacity 0.2s, transform 0.2s;
|
||||||
@@ -209,17 +209,47 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.subscribe-btn svg {
|
.subscribe-btn svg {
|
||||||
width: 18px;
|
width: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-hiw { background: var(--accent); }
|
|
||||||
.btn-spotify { background: #1DB954; }
|
.btn-spotify { background: #1DB954; }
|
||||||
.btn-youtube { background: #FF0000; }
|
.btn-youtube { background: #FF0000; }
|
||||||
.btn-apple { background: #A033FF; }
|
.btn-apple { background: #A033FF; }
|
||||||
.btn-discord { background: #5865F2; }
|
|
||||||
.btn-rss { background: #f26522; }
|
/* Secondary links — How It Works, Discord, RSS */
|
||||||
|
.secondary-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.25rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 0.3rem 0.85rem;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-link:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-link svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Episodes */
|
/* Episodes */
|
||||||
.episodes-section {
|
.episodes-section {
|
||||||
@@ -1166,6 +1196,10 @@ a:hover {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary-links {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.episodes-section {
|
.episodes-section {
|
||||||
padding: 2rem 2rem 3rem;
|
padding: 2rem 2rem 3rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,10 @@
|
|||||||
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" href="favicon.ico" sizes="48x48">
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
@@ -89,6 +92,10 @@
|
|||||||
<a href="/how-it-works">How It Works</a>
|
<a href="/how-it-works">How It Works</a>
|
||||||
<a href="/stats">Stats</a>
|
<a href="/stats">Stats</a>
|
||||||
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
||||||
|
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener">Facebook</a>
|
||||||
|
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener">X</a>
|
||||||
|
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener">Bluesky</a>
|
||||||
|
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener">Mastodon</a>
|
||||||
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
||||||
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
||||||
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
||||||
@@ -102,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
||||||
<p>© 2026 Luke at the Roost</p>
|
<p>© 2026 Luke at the Roost · <a href="/privacy">Privacy Policy</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Sticky Audio Player -->
|
<!-- Sticky Audio Player -->
|
||||||
|
|||||||
BIN
website/favicon-192.png
Normal file
BIN
website/favicon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
website/favicon-48.png
Normal file
BIN
website/favicon-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
website/favicon.ico
Normal file
BIN
website/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -19,7 +19,10 @@
|
|||||||
<meta name="twitter:description" content="The tech behind a one-of-a-kind AI radio show: real-time caller generation, multi-stem recording, automated post-production, and global CDN distribution.">
|
<meta name="twitter:description" content="The tech behind a one-of-a-kind AI radio show: real-time caller generation, multi-stem recording, automated post-production, and global CDN distribution.">
|
||||||
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||||
|
|
||||||
|
<link rel="icon" href="favicon.ico" sizes="48x48">
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
@@ -501,6 +504,10 @@
|
|||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
<a href="/stats">Stats</a>
|
<a href="/stats">Stats</a>
|
||||||
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
||||||
|
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener">Facebook</a>
|
||||||
|
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener">X</a>
|
||||||
|
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener">Bluesky</a>
|
||||||
|
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener">Mastodon</a>
|
||||||
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
||||||
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
||||||
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
||||||
@@ -514,7 +521,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
||||||
<p>© 2026 Luke at the Roost</p>
|
<p>© 2026 Luke at the Roost · <a href="/privacy">Privacy Policy</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -21,7 +21,10 @@
|
|||||||
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" href="favicon.ico" sizes="48x48">
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
@@ -91,27 +94,29 @@
|
|||||||
<span class="phone-digits">(208-439-5853)</span>
|
<span class="phone-digits">(208-439-5853)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="subscribe-row">
|
<div class="subscribe-row">
|
||||||
<a href="/how-it-works" class="subscribe-btn btn-hiw">
|
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/></svg>
|
|
||||||
How It Works
|
|
||||||
</a>
|
|
||||||
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="subscribe-btn btn-spotify">
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="subscribe-btn btn-spotify">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>
|
||||||
Spotify
|
Spotify
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="subscribe-btn btn-youtube">
|
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
|
||||||
YouTube
|
|
||||||
</a>
|
|
||||||
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="subscribe-btn btn-apple">
|
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="subscribe-btn btn-apple">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.34 0A5.328 5.328 0 0 0 0 5.34v13.32A5.328 5.328 0 0 0 5.34 24h13.32A5.328 5.328 0 0 0 24 18.66V5.34A5.328 5.328 0 0 0 18.66 0zm6.525 2.568c2.336 0 4.448.902 4.448 3.545 0 1.497-.89 2.67-1.916 3.545-.663.566-.795 .84-.795 1.347 0 .6.397 1.173.894 1.722 1.417 1.564 1.96 2.853 1.96 4.448 0 3.063-2.673 4.257-5.165 4.257-.315 0-.658-.02-.994-.063-1.523-.195-2.86-.9-3.632-.9-.82 0-1.98.623-3.377.87A5.715 5.715 0 0 1 3.15 21.4c-1.27 0-2.1-.96-2.1-2.663 0-1.2.6-2.7 1.845-4.29.63-.81 1.62-1.83 2.91-2.31-.06-.6-.09-1.14-.09-1.62 0-4.28 2.76-7.95 6.15-7.95z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.34 0A5.328 5.328 0 0 0 0 5.34v13.32A5.328 5.328 0 0 0 5.34 24h13.32A5.328 5.328 0 0 0 24 18.66V5.34A5.328 5.328 0 0 0 18.66 0zm6.525 2.568c2.336 0 4.448.902 4.448 3.545 0 1.497-.89 2.67-1.916 3.545-.663.566-.795 .84-.795 1.347 0 .6.397 1.173.894 1.722 1.417 1.564 1.96 2.853 1.96 4.448 0 3.063-2.673 4.257-5.165 4.257-.315 0-.658-.02-.994-.063-1.523-.195-2.86-.9-3.632-.9-.82 0-1.98.623-3.377.87A5.715 5.715 0 0 1 3.15 21.4c-1.27 0-2.1-.96-2.1-2.663 0-1.2.6-2.7 1.845-4.29.63-.81 1.62-1.83 2.91-2.31-.06-.6-.09-1.14-.09-1.62 0-4.28 2.76-7.95 6.15-7.95z"/></svg>
|
||||||
Apple
|
Apple
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="subscribe-btn btn-discord">
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="subscribe-btn btn-youtube">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||||
|
YouTube
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="secondary-links">
|
||||||
|
<a href="/how-it-works" class="secondary-link">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/></svg>
|
||||||
|
How It Works
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="secondary-link">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.947 2.418-2.157 2.418z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.947 2.418-2.157 2.418z"/></svg>
|
||||||
Discord
|
Discord
|
||||||
</a>
|
</a>
|
||||||
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="subscribe-btn btn-rss">
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="secondary-link">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248S0 22.546 0 20.752s1.456-3.248 3.252-3.248 3.251 1.454 3.251 3.248zM.002 9.473v4.594c5.508.163 9.929 4.584 10.092 10.091h4.594C14.524 16.21 7.849 9.636.002 9.473zM.006 0v4.604C10.81 4.77 19.23 13.19 19.396 24h4.604C23.834 10.952 13.054.166.006 0z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248S0 22.546 0 20.752s1.456-3.248 3.252-3.248 3.251 1.454 3.251 3.248zM.002 9.473v4.594c5.508.163 9.929 4.584 10.092 10.091h4.594C14.524 16.21 7.849 9.636.002 9.473zM.006 0v4.604C10.81 4.77 19.23 13.19 19.396 24h4.604C23.834 10.952 13.054.166.006 0z"/></svg>
|
||||||
RSS
|
RSS
|
||||||
</a>
|
</a>
|
||||||
@@ -208,6 +213,10 @@
|
|||||||
<a href="/how-it-works">How It Works</a>
|
<a href="/how-it-works">How It Works</a>
|
||||||
<a href="/stats">Stats</a>
|
<a href="/stats">Stats</a>
|
||||||
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
||||||
|
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener">Facebook</a>
|
||||||
|
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener">X</a>
|
||||||
|
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener">Bluesky</a>
|
||||||
|
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener">Mastodon</a>
|
||||||
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
||||||
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
||||||
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
||||||
@@ -221,7 +230,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
||||||
<p>© 2026 Luke at the Roost</p>
|
<p>© 2026 Luke at the Roost · <a href="/privacy">Privacy Policy</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Sticky Audio Player -->
|
<!-- Sticky Audio Player -->
|
||||||
|
|||||||
113
website/privacy.html
Normal file
113
website/privacy.html
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Privacy Policy — Luke at the Roost</title>
|
||||||
|
<meta name="description" content="Privacy policy for Luke at the Roost podcast and website.">
|
||||||
|
<meta name="theme-color" content="#1a1209">
|
||||||
|
<link rel="canonical" href="https://lukeattheroost.com/privacy">
|
||||||
|
|
||||||
|
<link rel="icon" href="favicon.ico" sizes="48x48">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
||||||
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/style.css?v=2">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Nav -->
|
||||||
|
<nav class="page-nav">
|
||||||
|
<a href="/" class="nav-home">Luke at the Roost</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="page-header">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p class="page-subtitle">Last updated: February 12, 2026</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="hiw-section" style="max-width: 740px; margin: 0 auto;">
|
||||||
|
<div style="line-height: 1.7; color: var(--text-secondary, #b8a88a);">
|
||||||
|
|
||||||
|
<h2>Who We Are</h2>
|
||||||
|
<p>Luke at the Roost is a podcast and website operated by MacNeil Media Group. Our website is <strong>lukeattheroost.com</strong>.</p>
|
||||||
|
|
||||||
|
<h2>Information We Collect</h2>
|
||||||
|
<h3>Website Visitors</h3>
|
||||||
|
<p>We use Cloudflare Web Analytics, which collects anonymous, aggregated usage data (page views, referrers, country). It does not use cookies, does not track individual users, and does not collect personal information.</p>
|
||||||
|
|
||||||
|
<h3>Podcast Listeners</h3>
|
||||||
|
<p>When you download or stream an episode, standard server logs may record your IP address and user agent. We use this data only for aggregate download statistics. We do not sell or share this data with third parties.</p>
|
||||||
|
|
||||||
|
<h3>Phone Callers</h3>
|
||||||
|
<p>If you call in to the show at 208-439-LUKE, your voice may be recorded and included in a published episode. By calling in, you consent to being recorded and broadcast. We do not collect or store your phone number beyond what is necessary for call routing.</p>
|
||||||
|
|
||||||
|
<h3>Social Media</h3>
|
||||||
|
<p>We maintain a presence on platforms including Facebook, YouTube, Spotify, and Discord. When you interact with us on these platforms, their respective privacy policies apply. We may use third-party tools to schedule and manage social media posts.</p>
|
||||||
|
|
||||||
|
<h2>Cookies</h2>
|
||||||
|
<p>Our website does not set any first-party cookies. Third-party services (such as embedded podcast players) may set their own cookies according to their policies.</p>
|
||||||
|
|
||||||
|
<h2>Third-Party Services</h2>
|
||||||
|
<p>We use the following third-party services:</p>
|
||||||
|
<ul style="margin: 0.5em 0 1em 1.5em;">
|
||||||
|
<li><strong>Cloudflare</strong> — CDN, DNS, and analytics</li>
|
||||||
|
<li><strong>BunnyCDN</strong> — Audio file delivery</li>
|
||||||
|
<li><strong>Spotify, Apple Podcasts, YouTube</strong> — Podcast distribution</li>
|
||||||
|
<li><strong>Discord</strong> — Community chat</li>
|
||||||
|
<li><strong>Facebook</strong> — Social media page</li>
|
||||||
|
</ul>
|
||||||
|
<p>Each service has its own privacy policy governing how they handle your data.</p>
|
||||||
|
|
||||||
|
<h2>Data Retention</h2>
|
||||||
|
<p>Aggregate analytics data is retained indefinitely. Server logs are retained for up to 90 days. Published episodes and transcripts are retained indefinitely as part of the public podcast archive.</p>
|
||||||
|
|
||||||
|
<h2>Children's Privacy</h2>
|
||||||
|
<p>Our content is rated explicit and is not directed at children under 13. We do not knowingly collect personal information from children.</p>
|
||||||
|
|
||||||
|
<h2>Your Rights</h2>
|
||||||
|
<p>If you have questions about your data or want to request removal of your voice from a published episode, contact us at <a href="mailto:luke@macneilmediagroup.com" style="color: var(--accent, #d4a44a);">luke@macneilmediagroup.com</a>.</p>
|
||||||
|
|
||||||
|
<h2>Changes</h2>
|
||||||
|
<p>We may update this policy from time to time. Changes will be posted on this page with an updated date.</p>
|
||||||
|
|
||||||
|
<h2>Contact</h2>
|
||||||
|
<p>MacNeil Media Group<br>
|
||||||
|
Email: <a href="mailto:luke@macneilmediagroup.com" style="color: var(--accent, #d4a44a);">luke@macneilmediagroup.com</a></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/stats">Stats</a>
|
||||||
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
||||||
|
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener">Facebook</a>
|
||||||
|
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener">X</a>
|
||||||
|
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener">Bluesky</a>
|
||||||
|
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener">Mastodon</a>
|
||||||
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
||||||
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
||||||
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
||||||
|
</div>
|
||||||
|
<div class="footer-projects">
|
||||||
|
<span class="footer-projects-label">More from Luke</span>
|
||||||
|
<div class="footer-projects-links">
|
||||||
|
<a href="https://macneilmediagroup.com" target="_blank" rel="noopener">MacNeil Media Group</a>
|
||||||
|
<a href="https://prints.macneilmediagroup.com" target="_blank" rel="noopener">Photography Prints</a>
|
||||||
|
<a href="https://youtube.com/lukemacneil" target="_blank" rel="noopener">YouTube</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
||||||
|
<p>© 2026 Luke at the Roost · <a href="/privacy">Privacy Policy</a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2,25 +2,25 @@
|
|||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://lukeattheroost.com</loc>
|
<loc>https://lukeattheroost.com</loc>
|
||||||
<lastmod>2026-02-12</lastmod>
|
<lastmod>2026-02-15</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://lukeattheroost.com/how-it-works</loc>
|
<loc>https://lukeattheroost.com/how-it-works</loc>
|
||||||
<lastmod>2026-02-11</lastmod>
|
<lastmod>2026-02-15</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://lukeattheroost.com/stats</loc>
|
<loc>https://lukeattheroost.com/stats</loc>
|
||||||
<lastmod>2026-02-12</lastmod>
|
<lastmod>2026-02-15</lastmod>
|
||||||
<changefreq>daily</changefreq>
|
<changefreq>daily</changefreq>
|
||||||
<priority>0.6</priority>
|
<priority>0.6</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://lukeattheroost.com/privacy</loc>
|
<loc>https://lukeattheroost.com/privacy</loc>
|
||||||
<lastmod>2026-02-12</lastmod>
|
<lastmod>2026-02-15</lastmod>
|
||||||
<changefreq>yearly</changefreq>
|
<changefreq>yearly</changefreq>
|
||||||
<priority>0.3</priority>
|
<priority>0.3</priority>
|
||||||
</url>
|
</url>
|
||||||
@@ -90,4 +90,10 @@
|
|||||||
<changefreq>never</changefreq>
|
<changefreq>never</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://lukeattheroost.com/episode.html?slug=episode-12-love-lies-and-loyalty</loc>
|
||||||
|
<lastmod>2026-02-14</lastmod>
|
||||||
|
<changefreq>never</changefreq>
|
||||||
|
<priority>0.7</priority>
|
||||||
|
</url>
|
||||||
</urlset>
|
</urlset>
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
<meta name="twitter:description" content="Podcast stats for Luke at the Roost — total downloads, unique listeners, Apple Podcasts reviews, YouTube views and subscribers, updated daily.">
|
<meta name="twitter:description" content="Podcast stats for Luke at the Roost — total downloads, unique listeners, Apple Podcasts reviews, YouTube views and subscribers, updated daily.">
|
||||||
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||||
|
|
||||||
|
<link rel="icon" href="favicon.ico" sizes="48x48">
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
@@ -58,6 +61,10 @@
|
|||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
<a href="/how-it-works">How It Works</a>
|
<a href="/how-it-works">How It Works</a>
|
||||||
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener">Discord</a>
|
||||||
|
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener">Facebook</a>
|
||||||
|
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener">X</a>
|
||||||
|
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener">Bluesky</a>
|
||||||
|
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener">Mastodon</a>
|
||||||
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener">Spotify</a>
|
||||||
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
|
||||||
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
|
||||||
@@ -71,7 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
<p class="footer-contact">Sales & Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
|
||||||
<p>© 2026 Luke at the Roost</p>
|
<p>© 2026 Luke at the Roost · <a href="/privacy">Privacy Policy</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
315
website/transcripts/episode-12-love-lies-and-loyalty.txt
Normal file
315
website/transcripts/episode-12-love-lies-and-loyalty.txt
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
LUKE: Welcome back to Look at the Roost. It is Saturday, February 14th, and today is a very special show. It's Valentine's Day. So thank you for tuning in and listening to us on your special day here. We're going to try something a little bit different. I'm going to read a dating profile, and I'm going to have the callers, let me know if this is a profile that they would select or not select. Would you go on a date with this person? Or not? First up, we have Reggie. Reggie, I'm going to read you a dating profile, and I want you to tell me if you would go on a date with this person or not.
|
||||||
|
|
||||||
|
REGGIE: Oh man, alright Luke, yeah, let's hear it. I got to warn you though, I haven't been in the dating game in God over 15 years. My wife and I got together when I was still in my 20s, so I don't even know what the hell I'm looking for anymore. But yeah, go ahead.
|
||||||
|
|
||||||
|
LUKE: Alright, well let's pretend that you are not married right now. You are looking through Tinder at dating profiles, and this is the one that maybe caught your eye. This is the one that you've landed on right now, it's crystal. She's 28 years old from Albuquerque. In her photo, she's standing behind the bar, mid-bore. It looks like she did not expect this picture to be taken. She's a little bit caught off guard, but she's pretty attractive. Her hair is up, she's going to tattoo on her forearm, some kind of script that I can't read, and there's a guy behind her at the bar waving for her attention, and she's not giving it to him. Her bio says, I work somewhere I'm not naming, I'm in school for synography, I go to bed at 3 and wake up at 6.45 and I have no personality left. Derek, if you see this, you owe me $11 for the hot pockets and the orange juice. You know what you did. Okay, so are you going to swipe right or left on crystal?
|
||||||
|
|
||||||
|
REGGIE: Oh man, I like her already, no seriously, I'm swiping right on crystal. Look, first of all, she's real, that profile is the most honest thing I've probably heard all night.
|
||||||
|
|
||||||
|
LUKE: Alright, and we have a match, what is it about crystal that you liked?
|
||||||
|
|
||||||
|
REGGIE: You know what it is, she's not trying to sell me something, like, I don't know what the hell synography is, ultra sounds or something?
|
||||||
|
|
||||||
|
LUKE: Yeah, I think so.
|
||||||
|
|
||||||
|
REGGIE: She's honest about being tired, she's got some beef with Derek over $11, which is hilarious, she's working her ass off, going to school, barely sleeping.
|
||||||
|
|
||||||
|
LUKE: Okay, so you swipe right on crystal, right is good, right, I don't even know how these things work. You swipe right on crystal, you're going to go on a date, where do you take her? What do you hope your evening looks like?
|
||||||
|
|
||||||
|
REGGIE: Okay, so here's the thing, she gets off work at 3 in the morning, right? So, I'm thinking, I take her to breakfast.
|
||||||
|
|
||||||
|
LUKE: Okay, you take her to breakfast at the diner that's open at 3 in the morning and she looks at you, like she wants you to order for her, what are you going to order for her?
|
||||||
|
|
||||||
|
REGGIE: Oh god, alright, um, okay, I'm getting her coffee first, that's obvious, she's running on fumes, then I'm ordering her the biggest, greasiest breakfast they got, like two eggs over easy, hash browns with cheese, bacon, toast. The whole deal, maybe Pancakes on the side if she wants them.
|
||||||
|
|
||||||
|
LUKE: Alright, well she loves the breakfast, and after breakfast she invites you back up to her room, what are you going to do when you get there?
|
||||||
|
|
||||||
|
REGGIE: Oh man, Luke, come on, look, I'm 38 years old, I've been out of the game forever, and honestly, if Crystal invites me back to her place after breakfast at 4 in the morning, I'm probably going to be so nervous, I'll just, I don't know, sit on her couch and keep talking to her. I mean, yeah, obviously part of me is thinking other things, but real talk? She's been up all night working, she just ate a huge breakfast, she's exhausted.
|
||||||
|
|
||||||
|
LUKE: Yeah, she is, but also real talk, Crystal hasn't been late in 14 months, and she's really desperate and she needs some energy in her life, so are you going to help her out here, or should she find somebody else?
|
||||||
|
|
||||||
|
REGGIE: Okay, okay, you got me there, yeah, I mean, if Crystal's looking for some energy, some connection, something to remind her she's alive and not just a zombie going from work to school to work, then yeah, I'm going to step up, I'm not going to let her down.
|
||||||
|
|
||||||
|
LUKE: Alright, and how do you plan to do that? What are you going to do for Crystal that she hasn't had in a while? How are you going to make yourself stand out in the crowd?
|
||||||
|
|
||||||
|
REGGIE: Alright, so here's what I'm thinking, Crystal's been grinding nonstop, right? She needs someone who's actually present, like I'm not going to be on my phone, I'm not going to rush anything, I'm going to pay attention to her, and honestly Luke, I've been told I give really good massages.
|
||||||
|
|
||||||
|
LUKE: Oh yeah, everybody's been told they've been given, they give really good massages, that's just the thing people say, almost nobody gives good massages.
|
||||||
|
|
||||||
|
REGGIE: No, but seriously, I actually learned from this physical therapist at the hospital, like I'm talking real technique here, I know where the knots are, I know how to work shoulders and neck properly, Crystal's been on her feet all night serving drinks to drunk idiots, her back is killing her, her feet are probably screaming, and look, maybe that sounds like I'm dodging the question, but honestly, that's the move, you take care of someone, you make them feel seen, you're not just trying to jump straight to the finish line like some desperate kid.
|
||||||
|
|
||||||
|
LUKE: Alright, it sounds like you and Crystal are a match, congratulations sir, and thank you for the call, very good, very good, see that we're making, we're making matches left and right here, who's up next on the line, we've got, Pearl, Pearl, welcome to the show, it's Valentine's Day, happy Valentine's Day, I'm going to read you a dating profile, it is a question of the real dating profile, and I want you to let me know if this is something that you'd go out with or not, okay?
|
||||||
|
|
||||||
|
PEARL: Oh man, okay Luke, I'm ready, hit me with it, and hey, happy Valentine's Day, date to you too, I'm spending mine calling a radio show, so you know, living my best life over here, but yeah, read it, let's see what we're working with.
|
||||||
|
|
||||||
|
LUKE: Alright, and there's nothing to feel bad about there, I'm spending my Valentine's Day doing a radio show, so we're in the same boat there, Pearl, okay, so here's your profile, it is Frank, Frank's photo, he's standing in a garage, and there's bird houses everywhere, they're on the shelves, they're hanging from the rafters, he's got them stacked up on the workbench, it's like, this is like a bird house picture with a guy in it, his bio says, my neighbor showed me how to make this and then left, I carried mail for 33 years, same route, a lady used to leave a coke on my porch every day for me in the summer, no no, I still think about that, my wife passed in 2020, I make bird houses now, I don't need 40 bird houses, but when my hands are busy, my head is quiet, I also make a decent meatloaf, if that matters to anyone, what do you think of Frank?
|
||||||
|
|
||||||
|
PEARL: Oh God, Luke, you can't, you can't do this to me right now, I would swipe right so fast, are you kidding me?
|
||||||
|
|
||||||
|
LUKE: Are you gonna have to remind me, Pearl, is, is, right yes or is right no?
|
||||||
|
|
||||||
|
PEARL: Right is yes, yes, Luke, right is yes, and I would swipe right on Frank so hard, I'd probably break my phone screen. Look, that man just said more in that profile than most guys say in six months of dating, when my hands are busy, my head is quiet, Jesus.
|
||||||
|
|
||||||
|
LUKE: All right, all right, that sounds great, congratulations on your match here, so is it the bird houses that does it for you or is it the quiet longing or is it the 63 years old bit? What is it that gets your engine turning? All right, so if you're gonna go out on a date with Frank here, where would you like him to take you?
|
||||||
|
|
||||||
|
PEARL: Oh man, I don't know, honestly, I'd probably just want to go to his garage and help him paint some bird houses or something, is that weird? Like, I don't need fancy, I've done fancy.
|
||||||
|
|
||||||
|
LUKE: How long before you let Frank take you up to the bedroom, Pearl?
|
||||||
|
|
||||||
|
PEARL: Luke, oh my God, I mean look, Frank seems like a gentleman.
|
||||||
|
|
||||||
|
LUKE: Okay, thank you for the call, Pearl. We appreciate it and we'll see if we can get Frank to reach out to you and maybe you two can spark something up. Thanks again for the call. You have a wonderful remainder of your Valentine's Day.
|
||||||
|
|
||||||
|
PEARL: Thank you, Luke. And hey, I heard Reggie earlier, the guy who was all nervous about the massage, tell him to just relax and actually listen to what she needs. Don't overthink it. Just be present.
|
||||||
|
|
||||||
|
LUKE: You hear you heard it here, Reggie. She says just be present and don't overthink it. Everything's going to be all right. And I believe her. She sounds like an honest soul. Okay, next up on the line we have Destiny. Oh, nope, that's not true. Next up on the line we have Mikey. Mikey, welcome to the show. I'm going to read you Destiny's profile pic, profile dating profile. And you let me know if that's something you'd go on a date with or not, okay?
|
||||||
|
|
||||||
|
MIKEY: Oh, man, okay, Luke. Yeah, I can do that. But listen, I got to be honest with you. My head is not really in the dating game right now. I just had the weirdest night of my life about an hour ago. And I'm still kind of processing it. But yeah, hit me with Destiny's profile. I'll tell you what I think.
|
||||||
|
|
||||||
|
LUKE: All right. Well, maybe Destiny can help you out with the processing your day's events. Here we go. Destiny is 31 years old. She's from Albuquerque. In her photo, she's in the front seat of what looks like a Nissan Altima. She's wearing scrubs or hairs up in a bun. She's got an ice coffee. And she's kind of just kind of staring towards the camera a little bit. She's not quite looking at it. She's just, she looks a little bit stoned, honestly. The bio says, I work at a chiropractor's office. Single mom, I drive an Altima with a dent from 2022. I am tired on a molecular level. I go to bed at nine. My daughter is six and she does not stop talking. I timed it once 11 minutes. No pauses. Please have your own place. My roommate is my mother and she listens through the wall. She says she doesn't. She is lying. Okay, there, Mikey. Are you going to swipe right or left on Destiny?
|
||||||
|
|
||||||
|
MIKEY: Oh, man, you know what? I'm swiping right on Destiny. Absolutely. That tired on a molecular level thing. I felt that in my soul, especially tonight, and the honesty about her mom listening through the wall.
|
||||||
|
|
||||||
|
LUKE: If you were at Destiny's place, what would her mom be listening to through the wall right now?
|
||||||
|
|
||||||
|
MIKEY: Oh, Jesus, Luke, I don't know, man. I mean, look, I'm 56 years old. I work in IT. I spend my days dealing with the city's network problems. If her mom's listening through the wall, she's probably hearing me talk about the time I accidentally brought down the entire Parks and Rec Departments email server for three hours because I clicked the wrong button. Real talk though. Real talk. She'd probably hear me venting about what happened tonight with my kid.
|
||||||
|
|
||||||
|
LUKE: All right, all right, all right, what happened with your kid?
|
||||||
|
|
||||||
|
MIKEY: So get this. It's 2 a.m., right? I can't sleep. I go to check on my 17 year old, just to make sure they're home safe, you know? The bedroom door is closed. I figured they're asleep. Something tells me to actually open the door and check.
|
||||||
|
|
||||||
|
LUKE: Okay, so you open the door and check and what happens?
|
||||||
|
|
||||||
|
MIKEY: Door opens. There's pillows under the blanket. Classic move, right? Like something out of a movie.
|
||||||
|
|
||||||
|
LUKE: Yup, that sounds like a 17 year old kid thing to do, and how did you react?
|
||||||
|
|
||||||
|
MIKEY: I freaked out, man. I immediately grabbed my phone, tried calling, straight to voicemail. So I'm thinking, okay, where would they go? I know my kid. I know their friends. I got in my truck and I just started driving to the usual spots. And Luke, I found them.
|
||||||
|
|
||||||
|
LUKE: All right, great, where did you find them?
|
||||||
|
|
||||||
|
MIKEY: Over at this house on the east side, one of their friends' older brother's places. I pull up and there's like six, seven cars in the driveway. Music going lights on. It's a full party, man. And here's the thing that's got me messed up. I look through the window before I go in and there's adults in there.
|
||||||
|
|
||||||
|
LUKE: All right, well, that's not good. How adults are we talking? Are we talking Jeffrey Epstein adult? Are we talking, you know, 21 adult?
|
||||||
|
|
||||||
|
MIKEY: No, no, no. Thank God, nothing like that. I'm talking like mid-twenties, maybe. One guy looked 30. But still, Luke, these are grown people throwing a party at 2 a.m.
|
||||||
|
|
||||||
|
LUKE: Well, that's the thing that grown people are, uh, want to do, especially on Valentine's Day weekend. So it's not that, uh, that odd. What did you do with your kid when you, when you found her there?
|
||||||
|
|
||||||
|
MIKEY: I went in there and man, I tried to stay cool. But I was pissed. I found my kid in the kitchen and they looked terrified when they saw me. I just said, we're leaving now. Didn't make a scene. Didn't yell at anybody else. Just got them out of there. The whole drive home dead silence. They're sitting there staring out the window. I'm gripping the steering wheel.
|
||||||
|
|
||||||
|
LUKE: All right, so everybody's home and safe now. And uh, crisis is averted. What are you going to do about the situation tomorrow? Are you going to grab the kid or is there some consequence for sneaking out? How are you going to handle it?
|
||||||
|
|
||||||
|
MIKEY: That's exactly why I'm calling man. I don't know. Part of me wants to ground them until their 30 take the phone the whole nine yards. But the other part of me is thinking, I was 17 once you know. I snuck out. I went to parties.
|
||||||
|
|
||||||
|
LUKE: Yep, so did I. That's part of growing up and I think as long as they were being relatively safe. I think that's a pretty alright thing and you should take it easy. Let them know you're disappointed in the whole lion and sneaking out thing. And next time, be more forthcoming but otherwise, I think you should maybe let this one go.
|
||||||
|
|
||||||
|
MIKEY: Yeah, but Luke, here's what's eating at me. It's not just that they snuck out. It's that there were adults there providing alcohol to minors. I saw the bottles. I saw the cups. These weren't kids raiding somebody's parents liquor cabinet. These were grown people who should know better hosting this thing.
|
||||||
|
|
||||||
|
LUKE: Yeah, and your kids 17 and you went to parties when you were 17 and I went to parties when I was 17 and it wasn't really a party unless there was adults there with real liquor and alcohol. Was it? Like, come on, 17 is not exactly 12. They're damn near drinking age anyway.
|
||||||
|
|
||||||
|
MIKEY: I mean, you're not wrong. Three more years and it's legal anyway. And yeah, the parties I went to at 17. There was always somebody's older cousin or some guy who just turned 20, one buying beer.
|
||||||
|
|
||||||
|
LUKE: And I think you just got to talk to your kid. Let them know that safety is important and give some guidance in that way and be there for them. But I don't think this deserves any sort of serious punishment. I think it's a pretty normal kid thing to do.
|
||||||
|
|
||||||
|
MIKEY: You know what, Luke, I hear you. I really do.
|
||||||
|
|
||||||
|
LUKE: Maybe I'm overreacting because I caught them and it freaked me out, seeing your kid in that situation. Even if it's normal, it just hits different when you're the parent. You know, I think you're right. I need to have a real conversation with them tomorrow. Not yelling, not lecturing, but actually talking. All right, well, thank you for the call. It sounds like you know what you need to do. I'm glad everybody's safe. And now let's hear a word from our sponsors.
|
||||||
|
|
||||||
|
Let's talk about what's going on below the belt. Saddle soft premium undergarments are made for fabrics so advanced. The military doesn't even know about it yet. We're talking about micro-modal cactus silk harvested by hand from prickly pear, which is ironic because the whole point is that nothing pricks you. Every pear is cut for the man who sits in a plastic chair in the desert from nine hours recording a podcast. That's a really use case. That's my use case. Saddle soft comes in three fits. Regular, generous, and mind your business. Use code Roost at checkout for 15% off your first pair. And no, I will not be modeling them. I've been asked. The answer is still no. Saddle soft. Your chair is uncomfortable enough.
|
||||||
|
|
||||||
|
All right, Bev. Bev, welcome to the show. Here's what we're doing. Happy Valentine's Day. I'm going to read you a dating profile and I want you to let me know if it's a yes or no.
|
||||||
|
|
||||||
|
BEV: All right. Oh, man, okay. Yeah, I'm game. Let's do this.
|
||||||
|
|
||||||
|
LUKE: All right, let's do this. Your profile is Terence. Terence is 31 years old in Albuquerque, New Mexico. In the photo, he's in a kitchen, in an apron, and it's really, really clean. It's like he's pointing. I skill it at the camera. In the bio, it says claims adjuster by day. At night, I cologne and narrate everything I'm doing in a British accent. There is no show. It's just me and Dolores, the skillet. My chiropractor told me to get a body pillow, and I named her Margarit. She's in some of the photos and I am not apologizing. Is Terence a yay or a nay for you?
|
||||||
|
|
||||||
|
BEV: Oh my god, I'm swiping right so hard. Are you kidding me? This is amazing. Okay, first of all, anyone who names their cast iron skillet is automatically husband material. Like, I respect that level of commitment to your cookware. And the British accent thing?
|
||||||
|
|
||||||
|
LUKE: Yeah, the British accent thing does it for you?
|
||||||
|
|
||||||
|
BEV: Hell yeah, it does. I mean, look, I'm picturing this guy just like narrating his whole evening like he's on the Great British baking show or something. And now we're going to sear the chicken to a lovely golden brown. Like, are you serious? That's adorable and hilarious at the same time.
|
||||||
|
|
||||||
|
LUKE: Okay, so if we were to set you up here with Terence, where would you where would you want to go?
|
||||||
|
|
||||||
|
BEV: Oh, okay, so here's the thing. I would want him to cook for me. Like, straight up, first date at his place. I know that sounds forward, but come on, the man's got Dolores, the skillet, and that abnormally clean kitchen. I want to see this in action. I want to hear the British accent narration live.
|
||||||
|
|
||||||
|
LUKE: All right, so you go over his house for your first date because you're a wonderful meal. It's delicious. You've never eaten anything like it before in your life. And things are going well. What happens next on your date?
|
||||||
|
|
||||||
|
BEV: Okay, so things are going well. The food was incredible. And I'm thinking I want to see if this guy's for real, you know? Like, is he actually this charming and weird in person? Or is it all an act?
|
||||||
|
|
||||||
|
LUKE: And how are you going to figure that out? What are you going to what's your assessment?
|
||||||
|
|
||||||
|
BEV: I'm going to ask to meet Margaret, the body pillow. Like, I need to know if he's actually got this thing prominently displayed or if it was just for the dating profile, you know? Because if Margaret's real and he's not embarrassed about it, then this dude is genuinely comfortable with himself.
|
||||||
|
|
||||||
|
LUKE: And that's okay. So you're going to have him cook you dinner and then you're going to have him take you to the bedroom and then what's going to happen?
|
||||||
|
|
||||||
|
BEV: Whoa. Okay, hold on, Luke. I said I want to Margaret, not like, I mean, yeah, she's in the bedroom probably. But I'm talking about seeing if he's actually as quirky and honest as he seems. Like, is this body pillow thing real? Or was it just a funny line?
|
||||||
|
|
||||||
|
LUKE: Well, it's in the picture. So it looks like it's real. So are you going to like get in the bed and give the body pillow a hug and test its snuggleness?
|
||||||
|
|
||||||
|
BEV: Ha. Okay, you know what? Yeah, I might. I might absolutely give Margaret a test hug just to see what the appeal is.
|
||||||
|
|
||||||
|
LUKE: All right. And then what is what's Terrence going to cook for you in the morning?
|
||||||
|
|
||||||
|
BEV: Oh, man. I didn't even think about breakfast. Okay, so if Terrence is as good as his profile, make some sound. I'm thinking maybe some kind of fancy eggs situation. Like, he seems like the type who do a proper English breakfast. You know, the whole deal with the beans and the tomatoes and the blood sausage and make French toast with that skillet of his narrating the whole thing to Dolores while I'm sitting there with coffee trying not to laugh. But honestly, I'd be happy with anything as long as he's still being himself and not suddenly getting all weird and performative about it being the morning after. You know what I mean?
|
||||||
|
|
||||||
|
LUKE: Yeah. Okay. Now, let's say you get to Terrence's house and he makes you dinner and it's not good. You don't like it at all. And he's very offended that you don't like the food that he cooked for you. And things take a strange and unpleasant turn. How are you going to handle the situation?
|
||||||
|
|
||||||
|
BEV: Okay. So first of all, I'm going to be honest, but not brutal about it. Right? Like, hey, this isn't really my thing, but I appreciate the effort. But if he gets offended, like actually upset that I'm not worshiping his cooking, that's a red flag the size of New Mexico, Luke.
|
||||||
|
|
||||||
|
LUKE: Yes, it is. And that's good. It sounds like a healthy response. Thank you for calling in, Bev. Did you have anything else you wanted to talk about tonight?
|
||||||
|
|
||||||
|
BEV: Oh, actually, yeah, I did call in for something else. So this is going to sound random, but I just watched this documentary about black holes and particle physics. And I cannot stop thinking about it. Like, I work at the DMV, right?
|
||||||
|
|
||||||
|
LUKE: Right. What's it about black holes or was it about particle physics? And what's that got to do with the DMV?
|
||||||
|
|
||||||
|
BEV: Okay. So it was about both. Like how black holes warp space time and all this stuff about particles that exist and don't exist at the same time. And what it's got to do with the DMV is absolutely nothing. That's the point. I spend all day processing registrations and listening to people complain about their license photos. And then I go home and my brain is just hungry for something bigger, you know? Like, I'm sitting there learning about how time literally slows down near a black hole. And I'm thinking about how my days at work feel like they're moving in slow motion anyway. But this stuff actually matters.
|
||||||
|
|
||||||
|
LUKE: I guess that depends on your perspective. I mean, one could say that your work at the DMV actually matters more than whether time dilates around a black hole. But, you know, it's okay to be interested in more than one thing. You can do your job. That's your job. It's just work. And then the whole rest of the universe is open for you when you're not at work. And you need both things in your life to be a well-rounded and healthy person.
|
||||||
|
|
||||||
|
BEV: Yeah, you're right. I mean, I know people need their licenses and their registrations. That stuff matters for real life. But God, Luke, it's just so mundane sometimes. Like today, Valentine's Day, everyone's coming in stressed about their expire tags or whatever. And I'm sitting there thinking about how there's literally particles popping in and out of existence at the edge of black holes. It's called Hawking Radiation. Isn't that wild? And I guess what I'm realizing is I've been so focused on just getting through my days, my second marriage, making things work after my first one crashed and burned. But I forgot I'm allowed to be curious about things.
|
||||||
|
|
||||||
|
LUKE: You are absolutely allowed to be curious about things. And if you're curious about Hawking Radiation, you should listen to yesterday's episode when we talked about it for a good bit. But thank you for the call. It's been a good one. And I hope you have a nice rest of your Valentine's Day weekend.
|
||||||
|
|
||||||
|
BEV: Oh, shit. You talked about it yesterday? I missed it. Okay, I'm definitely going back to listen to that.
|
||||||
|
|
||||||
|
LUKE: Alright, thanks for the call, Bev. And now we've got next up is Rick. Rick, welcome to the show. I'm going to read you this dating profile. I want you to let me know if you'd go on a date with this person or if you would just pass.
|
||||||
|
|
||||||
|
RICK: Hey, Luke, good to be back on. All right, lay it on me. Let's hear this profile.
|
||||||
|
|
||||||
|
LUKE: Alright, your match is Angela. She's 29 years old and she lives in Edgwood, Montana. In her photo, it's a picture of her in a white SUV. She's got her seat belt on. She's got Starbucks full makeup. It looks like morning. So she's probably going to work, I would guess. She's got the face of a woman who's about to go live on Instagram. Her vibes are mirror is down. And then she's got some other photos here. You can see there's a the back window there. It's got a little stick figure family. Something in cursive that says blessed. Her bio says Christian conservative girl mom, small business owner, ask me about Lumi glow. It's not a pyramid scheme. There's literally a chart. I will show you the chart. I wake up at 530 because my highlight doesn't do itself. I need a man who braze provides and protects in that order. If you don't have a relationship with God, we need to talk about that first. My daughter is my whole world. I also sell skincare. And it changed my life. So what do you think of Angela, Rick?
|
||||||
|
|
||||||
|
RICK: Oh man, Luke, listen, I got to be honest with you. I'm swiping left so fast I might break my phone. Look, nothing against her. She seems like she's got her life together, right? She's up at 530. She's running a business. I respect the hustle.
|
||||||
|
|
||||||
|
LUKE: So why is it a no for you, sir?
|
||||||
|
|
||||||
|
RICK: Listen, it's a couple things. First off, I'm 65 years old. She's 29. I could be her grandfather, Luke.
|
||||||
|
|
||||||
|
LUKE: That's just that sounds like one of them good problems to have, sir.
|
||||||
|
|
||||||
|
RICK: Oh, come on now. No, but seriously, Luke, the ask me about Lumi glow thing. That right there tells me I'm going to spend every day hearing about skincare products and how I need to be a distributor or whatever. She says it's not a pyramid scheme and then immediately mentions a brother. If you got to show me a chart to prove it's not a pyramid scheme, it's a pyramid scheme. And look, I got nothing against faith. I was raised Catholic. My ex-wife Diane was pretty religious, but when someone leads with if you don't have a relationship with God, we need to talk about that first. That's...
|
||||||
|
|
||||||
|
LUKE: Yeah, I don't disagree with you. So what would it take for you to give Angela a chance, even though there's this dramatic age gap?
|
||||||
|
|
||||||
|
RICK: Oh, man, you're really going to make me think about this, huh? All right, listen, if Angela came at me without the sales pitch, that'd be a start. Like if she just said, hey, I run a small business and left it at that, I could respect it. And if she dialed back the intensity on the God talk, I mean, I'm not against it, but maybe we could just have coffee first before we get into my eternal soul, you know?
|
||||||
|
|
||||||
|
LUKE: That sounds very reasonable, sir. Thanks for the call. We appreciate it, but now it's time for another word from another one of our sponsors. All right, I'm going to read this ad exactly as they sent it to me, and I want you to know that I have concerns. Phantom Ranch Wellness wants you to know that your gut health starts, and I'm reading this directly from the card now, from the other end. They make an at-home coffee enemy kit. It comes in a box that says, wellness inside on the outside, which I think we can agree is doing a lot of the heavy lifting. The kit includes a medical grade silicone bag, organic single origin coffee, because apparently the coffee you put in here, that coffee needs to be single origin, a detailed instruction booklet, and a com card with a QR code to a guided meditation for daring. During. There's a guided meditation for daring. I'm not going to tell you what to do with your body. That's between you and your bathroom and whatever God you answer to. I will say the reviews are disturbingly positive. Four and a half stars. One guy said it has changed his morning routine, which I believe, because it would absolutely change your morning routine. Phantom Ranch Wellness. I read the ad. I didn't endorse it. Okay, and welcome back to the show. Sheila, Sheila, welcome to the show. Happy Valentine's Day. I'm going to read you this Tinder profile, and I want you to let me know if it's a pass or a fail for you, okay?
|
||||||
|
|
||||||
|
SHEILA: Oh God, okay. Yeah, let me hear it. I could use a laugh tonight, honestly. Go ahead.
|
||||||
|
|
||||||
|
LUKE: Okay, your match is Greg. He's 44 years old from Rio Rancho, New Mexico. In his photo, he's got wrap around Oakley's, but they're on his head, not his face. They're just kind of sitting up there. He's in the driver's seat of a truck. It looks like a ram, and he took this selfie from below, which is the worst possible angle. He's got to go tease, wearing an under-armor, looking kind of polo thing. There's a Punisher Skull sticker on the dashboard, and one of those little tree air fresheners. His other photos are the truck from the outside, a fish, a gym selfie, and a photo with his kids where their faces are scribbled out with a pen tool. His bio says, work hard, play hard, dad of two. I back into it every spot. I will hold the door open for you, whether you like it or not. Not here for games or drama. If you can't handle me in my worst, you don't deserve me at my best. Law, probably swiped right for your dog. Six oh, since that matter is apparently truck guy, gym five times a week, upper body in parentheses, looking for a woman, not a girl. Is Sheila, what do you have to say about Greg?
|
||||||
|
|
||||||
|
SHEILA: Oh Jesus Christ, Luke, that's a hard pass. Like, I'm not even being mean, but come on. First of all, the Oakley's on top of his head.
|
||||||
|
|
||||||
|
LUKE: What you got against Oakleys?
|
||||||
|
|
||||||
|
SHEILA: Nothing. Nothing against Oakleys, but when they're just sitting up there like a headband, not even being used, that's a choice. But honestly, it's the whole vibe. If you can't handle me at my worst, dude, that's a red flag the size of New Mexico.
|
||||||
|
|
||||||
|
LUKE: Yeah, I heard that a couple times tonight. Red flag the size of New Mexico. It's a pretty big red flag. So Sheila, what would you be looking for in one of these dating profiles? What would you think and what would make you want to take one of these gentlemen out?
|
||||||
|
|
||||||
|
SHEILA: Wait, hold on. I'm not actually on the market, Luke. I've been with my boyfriend for three years now. I just thought this was a fun game.
|
||||||
|
|
||||||
|
LUKE: Yeah, it is. Your boyfriend don't get nothing to do with this. What's your man get to do with me? We're just, we're just hyper thysen, hypothetical eyes in here. So imagine that you didn't have the boyfriend and you were just swiping Tinder on Valentine's Day weekend. What would you be looking for? What would, what would reach out to you? What would speak to you?
|
||||||
|
|
||||||
|
SHEILA: Okay. Okay. Hypothetically, I mean, I guess I'd want someone who's actually honest, you know? Not trying to be some tough guy or whatever. Like just say what you actually do with your time. If you make bird houses or you're into weird documentaries or whatever, just own it.
|
||||||
|
|
||||||
|
LUKE: Okay, well, you're a current boyfriend now. What was his dating profile like when you met him?
|
||||||
|
|
||||||
|
SHEILA: Oh God, we didn't meet online. We met at work actually. I was doing some taxidermy work for this hunting lodge and he was doing electrical stuff there. So no profile, just him being normal in person, I guess. Which is probably better because I don't know if I would have swiped right on anyone. Honestly, the whole thing seems exhausting.
|
||||||
|
|
||||||
|
LUKE: Okay. Well, just because you met in person doesn't mean you didn't have a dating profile.
|
||||||
|
|
||||||
|
LUKE: I'm sure he did and I'm sure that you looked for it at one point during the courtship process because if you didn't, I think at this point that would be insane. You're going to know what what what your potential partner is doing out on the inner web is now.
|
||||||
|
|
||||||
|
CALLER: Okay, you got me there. Yeah, I looked. Of course, I looked.
|
||||||
|
|
||||||
|
LUKE: Okay, and what did it say?
|
||||||
|
|
||||||
|
CALLER: Oh man, it was so bad. Like, truly terrible. He had this one picture where he's holding a fish, which whatever, guys love that. But then another one where he's just standing in front of his truck with his arms crossed. Very serious.
|
||||||
|
|
||||||
|
LUKE: So if you had just stumbled across his dating profile, you wouldn't have given him the time of day, right? You would have said, this is a terrible profile. But since you met him in person, you saw he was a normal gentleman. You saw something that you wouldn't have seen in the profile. Do you think that's common?
|
||||||
|
|
||||||
|
CALLER: Yeah, absolutely. I mean, his bio was like two sentences, something about liking the outdoors and being laid back. Which, okay, everyone says that. There was nothing in there about how he's actually pretty funny when you get him talking or that he's good with his hands. Not like that, I mean, he can fix stuff or that he's patient with my dad, which is huge because my dad is a lot.
|
||||||
|
|
||||||
|
LUKE: So what you're saying is your boyfriend isn't very good with his hands?
|
||||||
|
|
||||||
|
CALLER: Oh my god, no, I meant like fixing things. Electrical work. He literally rewired half my house. Get your stuff. I'm not going to lie on the radio and say he's bad at other stuff either.
|
||||||
|
|
||||||
|
LUKE: All right, good for you. Well, it sounds like you made the right decision and you're in a happy relationship, so I hope you two have a wonderful remainder of your Valentine's Day weekend.
|
||||||
|
|
||||||
|
CALLER: Well, hold on. I didn't call to talk about my love life. I actually called because I'm dealing with this whole nightmare situation with my neighbor's dog. It bit my kid last week, and now the neighbor is saying my daughter provoked it, which is complete bullshit.
|
||||||
|
|
||||||
|
LUKE: How do you know it's bullshit?
|
||||||
|
|
||||||
|
CALLER: Because I know my kid. She's seven. She was literally just standing in our yard throwing a ball for our own dog. When their pit mix came barreling through the fence, which has been broken for like two months by the way, and I've asked them to fix it three times. The dog went straight for her and bit her arm. She needed stitches.
|
||||||
|
|
||||||
|
LUKE: And how did the owner of the dog react to this? Did she take care of the medical bills and stuff?
|
||||||
|
|
||||||
|
CALLER: He and no, he did not. He came over after it happened, saw the blood on her shirt, and his first words were, well, what did she do to him? Like my seven-year-old is out here antagonizing dogs for fun. Then he said his dog is protective and wouldn't just attack for no reason? I'm like, dude, she's a child in her own yard. That is no reason. Now animal control is involved because I reported it, and he's telling them this whole story about how she was teasing the dog with food, which never happened.
|
||||||
|
|
||||||
|
LUKE: Well, I don't think that matters anyway. If animal control is involved and the dog has bitten the child, I don't think it really matters if the child was teasing the dog or not.
|
||||||
|
|
||||||
|
CALLER: That's what I thought too, but apparently they're doing this whole investigation thing, and his version matters for whether the dog gets labeled dangerous or not. And he's got ring camera footage. He says, we'll prove his side, but he won't show it to me, only to animal control. Which makes me think he's full of it, because if he had proof my kid did something, wouldn't he be shoving it in my face? And the worst part is, my dad, who lives with us, has been over there twice now trying to handle it man-to-man, which just means he's yelling at the guy in his driveway.
|
||||||
|
|
||||||
|
LUKE: Oh, that sounds like an uncomfortable situation. That's no fun. I'm glad that your kid is okay, and hopefully the dog is okay too, and maybe you can resolve that situation a little more peacefully.
|
||||||
|
|
||||||
|
CALLER: Luke, I didn't call for you to both sides this. The dog bit my kid. There's no hopefully the dog is okay too. The dog is fine. My daughter has six stitches and is scared to play in her own yard now, and peaceful went out the window when this guy decided to lie about what happened instead of just apologizing and fixing his damn fence.
|
||||||
|
|
||||||
|
LUKE: Well listen, you don't get to tell me how to respond, and I like dogs, so I don't ever want to see dogs be harmed or punished. And it sounds like you don't have any proof that this other guy was in the wrong. So why don't you just back the fuck up there lady and take my both sides for what it's worth, which is nothing. You call the radio show on Valentine's Day, so go fuck yourself.
|
||||||
|
|
||||||
|
CALLER: Wow. Okay, you know what Luke? I called because I thought maybe someone out there listening has dealt with this kind of thing, and could tell me what to expect from animal control, but clearly you're more worried about the feelings of a dog that put a child in urgent care than the actual child.
|
||||||
|
|
||||||
|
LUKE: Yep, that is absolutely 100% correct. You got me there. That's the truth. All right Brenda, you're going to be our last caller. I'm going to give you a dating profile, and I want you to let me know if you would go for this person or not, okay?
|
||||||
|
|
||||||
|
BRENDA: Oh man, okay. Yeah, I'll play along. Hit me with it, Luke. Let's see what you got.
|
||||||
|
|
||||||
|
LUKE: Okay, your match is Chuck. He's 58 years old from Belen, New Mexico, in his photo he's standing in the driveway next to a boat on a trailer. It looks like the boat hasn't been in the water for a long time. There's leaves in it a bunch of them. It looks like there was a tarp on it at one point, but now it's just on the ground next to the boat, and the man standing there with his hands on his hips looking real proud. The bio says, I have a boat. It doesn't run. It's been in the driveway three years. My neighbor went to the HOA twice. The HOA came to talk to me in person. I'm retired. I have nothing but time. The motor needs work. The trailer needs tires. The electrical is shot, but the hull is solid, and that's what matters. My buddy Gary says sell it. Gary drives the camera. Gary doesn't have a dream.
|
||||||
|
|
||||||
|
BRENDA: Oh my god, okay, wait. So this man's entire dating profile is basically, I'm in a standoff with my HOA over a boat that doesn't work. You know what? I'm going to swipe right on Chuck. Here's why. This man has commitment.
|
||||||
|
|
||||||
|
LUKE: All right, so this is the commitment is going to do it for you, huh? You're going to go on a date with Chuck. You're going to wait for him to fix up that boat and take you out on the lake.
|
||||||
|
|
||||||
|
BRENDA: Yeah, I mean, look, I've been married twice. Look, both times to men who said they were going to do things and then didn't. At least Chuck is honest about the fact that his boat doesn't run and probably never will. That's refreshing, and you know what? Three years fighting the HOA.
|
||||||
|
|
||||||
|
LUKE: Yeah, so Brenda, do you have a dating profile right now?
|
||||||
|
|
||||||
|
BRENDA: No, no, I don't. I'm not really in that headspace right now, to be honest. I actually called in tonight because I've got something kind of wild going on, but I was happy to play your game first.
|
||||||
|
|
||||||
|
LUKE: All right, great. Well, we appreciate that. What's going on in your life? Why are you calling in tonight?
|
||||||
|
|
||||||
|
BRENDA: Okay, so my mom passed away about eight months ago and I've been going through her stuff, right? Cleaning out the house, sorting through everything. And a couple weeks ago, I got into her old email account because I was looking for some financial documents, insurance stuff, you know, and look, I found emails going back like 15 years to a woman named Patricia and it turns out my mom had another kid.
|
||||||
|
|
||||||
|
LUKE: Oh, yeah, how old's the kid?
|
||||||
|
|
||||||
|
BRENDA: She's not a kid anymore. She's 47. Her name is Diane. My mom gave her up for adoption in like 1977, way before she had me.
|
||||||
|
|
||||||
|
LUKE: Have you reached out to Diane at all? Have you made contact with your long-lost sister?
|
||||||
|
|
||||||
|
BRENDA: Not yet. That's the thing. I've been sitting on this for two weeks just completely freaked out. I found Diane on Facebook. She lives over in Tucson. She's got two kids, works at a credit union.
|
||||||
|
|
||||||
|
LUKE: Okay, well, are you going to try and make contact there and talk to her or are you going to let this one go?
|
||||||
|
|
||||||
|
BRENDA: Diane, yeah, I mean, that's why I'm calling. Look, I don't know if I should. Like, what do I even say? Hey, a mom died and I found out you exist while going through her emails. That sounds insane.
|
||||||
|
|
||||||
|
LUKE: It does sound a little insane but it's the truth and it is a little insane and maybe she wants to know. I mean, that's probably what I would do. I don't know how I would feel in that situation. I'd probably want to at least meet that person though.
|
||||||
|
|
||||||
|
BRENDA: Yeah, that's what keeps eating at me. Like she's my sister, half sister, whatever. She's blood. And from the emails, it looks like my mom and Patricia, that's the adoptive mom. They stayed in touch for years. They'd send updates, photos, sometimes.
|
||||||
|
|
||||||
|
LUKE: Well, it's really interesting. I'm sure your mom had a pretty good reason to give her up for adoption and it sounds like she tried to stay involved in her life so it wasn't just like a dump and run. So yeah, that's interesting but congratulations on finding a new sister. That's pretty cool.
|
||||||
|
|
||||||
|
BRENDA: I mean, yeah, when you say it like that, it sounds cool. But look, I'm also kind of pissed off. You know, my whole life, I thought I wasn't only child. My mom never said a word about this.
|
||||||
|
|
||||||
|
LUKE: Sure, but it wasn't about you. You weren't even around yet. So I'm sure that she was just trying to protect what's her name, Diane, from finding out that she was adopted and having the most normal life she possibly could. So I'm sure your mom had a reason. It sounds like she wasn't she wasn't being an asshole about it, but she for whatever reason couldn't couldn't take advocate at that time.
|
||||||
|
|
||||||
|
BRENDA: You're right. You're right. I know that in my head. Mom was only 21 when she had Diane. No money, no support. Her parents had already kicked her out. I get why she did it.
|
||||||
|
|
||||||
|
LUKE: Yeah, well, maybe maybe you could go meet up with Chuck and you and Chuck could go meet up with Diane and then you could all hash it out together and become a new happy family.
|
||||||
|
|
||||||
|
BRENDA: Oh God, no. Chuck's not in the picture anymore. He died like 10 years ago. I saw his obituary when I was doing all the stigging. Heart attack at 58. Never married. No other kids that I could find.
|
||||||
|
|
||||||
|
LUKE: Well, I don't know if we're talking about the same Chuck there because I'm talking about the Chuck that I just read to you. The dating profile. Like I have from Belen with a boat. I use also 58 though. So that's quite a coincidence.
|
||||||
|
|
||||||
|
BRENDA: Wait, what? Luke, I didn't. I've been so in my head about this Diane thing. I wasn't even listening to that part. I called in about my sister's situation, not for dating profiles. So honestly, at this point, maybe I need the distraction. What kind of boat does this Chuck have?
|
||||||
|
|
||||||
|
LUKE: Okay, well, you sound a little bit distracted. You're going to have to go back and listen to the show. And we've run out of time. This is the end of our Valentine's Day special. I hope you have a wonderful rest of your night and reach out to that long-lost sister and see if you can maybe came to a relationship. Maybe not. Who knows? But it's been another wonderful and illuminating episode of Luke at the Roost tonight. Thanks for tuning in. And I hope you all have a great evening. Well, bye.
|
||||||
Reference in New Issue
Block a user