45 Commits

Author SHA1 Message Date
luke c7aac1b373 Make AI callers more dynamic with arcs, seeds, style gradients, and learning
- Conversation arc system: callers evolve through Opening → Building → Turn →
  Depth → Wind down stages, with style-based timing offsets
- Problem seeds: 80 short concept strings that the LLM expands into unique
  stories each time (50% of PROBLEMS picks use seeds vs full entries)
- Style gradients: 4 composable axes (energy, honesty, humor, confidence)
  with 625 combinations, used 50% of the time alongside handcrafted styles
- Call duration tracking: records metrics to data/call_metrics.json, learns
  which pools and style axes produce longer calls, nudges future picks ±15%

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 06:21:09 -06:00
luke f7b75fa72f Clips page, new episodes, TTS/audio improvements, publish pipeline updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 05:38:58 -06:00
luke 2c7fcdb5ae Move hardcoded secrets to .env, add .env.example
Castopod password, DB password, BunnyCDN keys, Postiz JWT/IDs,
and monitoring token all moved to environment variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:19:20 -07:00
luke 0bdac16250 Upgrade Whisper to distil-large-v3, fix caller identity confusion, sort clips list
- Whisper base → distil-large-v3 for much better live transcription accuracy
- Add context hints to transcription (caller name, screening status)
- Increase beam_size 3→5 for better decoding
- Add explicit role clarification in caller system prompt so LLM knows Luke is the host
- Prefix host messages with [Host Luke] in LLM conversation
- Fix upload_clips episode list sorting (natural numeric order)
- Episodes 26-28 transcripts, data updates, misc fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:46:51 -07:00
luke 6eeab58464 TTS fixes, Inworld improvements, footer redesign, episodes 15-25, invoice script fix
- Fix TTS text pipeline: new caps handling (spell out unknown acronyms, lowercase
  emphasis words), action-word lookahead for parenthetical stripping, abbreviation
  expansions (US→United States, NM→New Mexico), pronunciation fixes
- Inworld TTS: camelCase API fields, speakingRate per-voice overrides, retry logic
  with exponential backoff (3 attempts)
- Footer redesign: SVG icons for social/podcast links across all pages
- Stats page: show "Rate us on Spotify" instead of "not public" placeholder
- New voices, expanded caller prompts and problem scenarios
- Social posting via Postiz, YouTube upload in publish pipeline
- Episode transcripts 15-25, terms page, sitemap updates
- Fix invoice script: match Timing totals using merged Task+App intervals

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:38:58 -07:00
luke 08a35bddeb Play idents in stereo on channels 15/16 with configurable ident_channel setting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:28:26 -07:00
luke bbcf767a8f Add idents playback section — loads from idents/ folder, plays on ads channel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:24:40 -07:00
luke b1bd4ed365 Add direct YouTube upload to publish pipeline, publish ep14
Bypass flaky YouTube RSS ingestion by converting MP3+cover to MP4
and uploading via YouTube Data API. Videos are auto-added to the
podcast playlist. Includes yt_auth.py for token management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 15:07:16 -07:00
luke 2b3551cada Add paragraph spacing on how-it-works page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:35:58 -07:00
luke d611f60743 SFX emojis, non-blocking email view, deploy/git docs in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:34:25 -07:00
luke d85a8d4511 Add listener email system with IMAP polling, TTS playback, and show awareness
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:22:56 -07:00
luke f0271e61df Clip pipeline improvements, direct YouTube upload, hero redesign, how-it-works updates
- make_clips: migrate refine_clip_timestamps to mlx-whisper, add LLM caption
  polishing, fix speaker label reversal in grouped caption lines
- upload_clips: interactive episode/clip/platform menus, direct YouTube Shorts
  upload via Data API v3 (bypasses Postiz), direct Bluesky upload
- Website hero: centered layout with left-column cover art on desktop, compact
  text links instead of pill buttons, scaled up typography
- How-it-works: move anatomy section above diagram, update stats (320 names,
  189+ personality layers, 20 towns, 570+ topics, 1400+ scenarios), add
  drunk/high/unhinged callers, voicemails, MLX Whisper GPU, LLM-polished captions
- All footers: add System Status link, remove Ko-fi branding
- .gitignore: YouTube OAuth credential files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 04:06:23 -07:00
luke 3164a70e48 Ep13 publish, MLX whisper, voicemail system, hero redesign, massive topic expansion
- Switch whisper transcription from faster-whisper (CPU) to lightning-whisper-mlx (GPU)
- Fix word_timestamps hanging, use ffprobe for accurate duration
- Add Cloudflare Pages Worker for SignalWire voicemail fallback when server offline
- Add voicemail sync on startup, delete tracking, save feature
- Add /feed RSS proxy to _worker.js (was broken by worker taking over routing)
- Redesign website hero section: ghost buttons, compact phone, plain text links
- Rewrite caller prompts for faster point-getting and host-following
- Expand TOPIC_CALLIN from ~250 to 547 entries across 34 categories
- Add new categories: biology, psychology, engineering, math, geology, animals,
  work, money, books, movies, relationships, health, language, true crime,
  drunk/high/unhinged callers
- Remove bad Inworld voices (Pixie, Dominus), reduce repeat caller frequency
- Add audio monitor device routing, uvicorn --reload-dir fix
- Publish episode 13

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:56:47 -07:00
luke 8d3d67a177 Add automated social clips section to how-it-works page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 04:43:31 -07:00
luke f9985fc693 Add direct Bluesky upload via atproto, bypass broken Postiz video
Postiz has a bug where Bluesky video uploads fail with "missing jobId".
This adds direct upload to Bluesky using the atproto SDK and the
video.bsky.app processing pipeline. Other platforms still use Postiz.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 04:34:15 -07:00
luke b02616bc44 Add clip generator, hourly stats cron, and transcription improvements
- make_clips.py: Extract best moments from episodes as short-form clips
  (9:16 vertical MP4 with captions for TikTok/Shorts/Reels)
- deploy_stats_cron.sh: Deploy podcast_stats.py to NAS as Docker container
  running hourly with auto-restart
- podcast_stats.py: Add _find_ytdlp() for Docker compatibility, auto-detect
  local Docker for Castopod DB queries
- publish_episode.py: Upgrade Whisper model from base to large-v3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:30:33 -07:00
luke 28af0723c7 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>
2026-02-14 22:53:34 -07:00
luke d78b1f7f4e Add episode 10+11 transcripts and update sitemap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:39:03 -07:00
luke 9fd977ad9f Postprod overhaul, control panel theme, caller names, website updates
- Fix denoise mangling host audio: strip aggressive afftdn/anlmdn, keep HPF only
- Add stem limiting for ads/SFX to prevent clipping
- Spoken-word compression on host (threshold -28dB, ratio 4:1)
- Add bus compressor on final stereo mix (LRA 7.9 → 5.7 LU)
- Drop SFX mix level from -6dB to -10dB
- De-esser fix: replace split-band with simple high-shelf EQ
- Pipeline now 15 steps (was 13)
- Control panel theme: match website warm brown/orange palette
- Expand caller names to 160 (80M/80F), fix duplicate name bug
- Update how-it-works page: returning callers, 15-step pipeline, remove busy diagram row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 04:39:31 -07:00
luke cb5665bca8 Add broadcast polish features to postprod pipeline
New 13-step pipeline:
- De-essing (split-band sibilance compression)
- Breath reduction (detect + attenuate by -12dB)
- HPF integrated into denoise step (80Hz rumble cut)
- Stereo imaging (host center, caller slight right, music Haas widening)
- Silence trimming (head/tail dead air removal)
- Fade in/out (equal-power sine curve, 1.5s/3.0s defaults)
- Auto chapter detection from stem activity
- Episode metadata (ID3 tags: title, artist, album, track, artwork)

Every new feature has a --no-* flag to disable individually.
Revert this commit to restore previous 9-step pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 04:02:47 -07:00
luke 95c2d06435 Postprod improvements: denoise, phone EQ, ad muting, ducking, voice mappings
- Add host mic noise reduction (afftdn + anlmdn)
- Add phone EQ bandpass on caller stem
- Mute music during ads with 2s lookahead/tail
- Increase ducking release to 3s to reduce pumping
- Add Inworld voice mappings for all regular callers
- Recording toggle endpoint, stem sync fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 03:59:08 -07:00
luke 75f15ba2d2 Add persistent caller voices, Discord, REC/on-air linking, SEO fixes, ep9
- Returning callers now keep their voice across sessions (stored in regulars.json)
- Backfilled voice assignments for all 11 existing regulars
- Discord button on homepage + link in all page footers
- REC and On-Air buttons now toggle together (both directions)
- Fixed host mic double-stream bug (stem_mic vs host_stream conflict)
- SEO: JSON-LD structured data on episode + how-it-works pages
- SEO: noscript fallbacks, RSS links, twitter meta tags
- Episode 9 transcript and sitemap update

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 00:24:37 -07:00
luke 953c501f75 Add stats page, SEO improvements, and auto-sitemap updates
- Add podcast_stats.py with --json/--upload flags for BunnyCDN
- Add website/stats.html fetching stats from CDN
- Add stats CSS styles
- SEO: shorten title/description, add og:site_name, twitter cards,
  theme-color, image dimensions, consistent favicons and cache-busting
- Add all episode transcript pages to sitemap.xml with lastmod
- Auto-add new episodes to sitemap in publish_episode.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:17:09 -07:00
luke cee78b5d88 Add speaker-labeled transcripts, favicon, host stream fix, episode page
- Re-label all 8 episode transcripts with LUKE:/CALLER: speaker labels
  using LLM-based diarization (relabel_transcripts.py)
- Add episode.html transcript page with styled speaker labels
- Update publish_episode.py to generate speaker-labeled transcripts
  and copy to website/transcripts/ for Cloudflare Pages
- Add SVG favicon with PNG fallbacks
- Fix CPU issue: tie host audio stream to on-air toggle, not per-caller
- Update how-it-works page with post-production pipeline info
- Add transcript links to episode cards in app.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:19:45 -07:00
luke 4becfd2122 Update audio settings and add new caller data from show session
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 13:43:41 -07:00
luke 7b7f9b8208 Add BunnyCDN integration, on-air website badge, publish script fixes
- On-air toggle uploads status.json to BunnyCDN + purges cache, website
  polls it every 15s to show live ON AIR / OFF AIR badge
- Publish script downloads Castopod's copy of audio for CDN upload
  (byte-exact match), removes broken slug fallback, syncs all episode
  media to CDN after publishing
- Fix f-string syntax error in publish_episode.py (Python <3.12)
- Enable CORS on BunnyCDN pull zone for json files
- CDN URLs for website OG images, stem recorder bug fixes, LLM token
  budget tweaks, session context in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:34:18 -07:00
luke 7d88c76f90 Add post-production pipeline: stem recorder, postprod script, recording UI
New stem recording system captures 5 time-aligned WAV files (host, caller,
music, sfx, ads) during live shows. Standalone postprod.py processes stems
into broadcast-ready MP3 with gap removal, voice compression, music ducking,
and EBU R128 loudness normalization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:53:32 -07:00
luke 356bf145b8 Add show improvement features: crossfade, emotions, returning callers, transcripts, screening
- Music crossfade: smooth 3-second blend between tracks instead of hard stop/start
- Emotional detection: analyze host mood from recent messages so callers adapt tone
- AI caller summaries: generate call summaries with timestamps for show history
- Returning callers: persist regular callers across sessions with call history
- Session export: generate transcripts with speaker labels and chapter markers
- Caller screening: AI pre-screens phone callers to get name and topic while queued

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 02:43:01 -07:00
luke de5577e582 Add local food opinions, nostalgia, and show history reactions
Callers now have strong food opinions (Sparky's green chile, Blake's
Lotaburger, etc.), nostalgic memories of how their town used to be,
and 60% chance of having a strong reaction to a previous caller that
they bring up early in the call by name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 01:51:39 -07:00
luke c789069f6f Add weather, time, season, and situational context for callers
Weather lookup via Open-Meteo API with 30-min cache, time/day
awareness (Mountain time), moon phase calculation, seasonal context
with local events, and probabilistic situational details: road
context, phone situation, background music, recent errands, TV tonight.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 01:46:27 -07:00
luke bd6c8ccbab Landing page: testimonials slider, how-it-works page, 25 TTS voices
- Add testimonial slider with 8 fake caller reviews
- Add how-it-works page with visual architecture diagram
- Expand voice pools: Inworld 25 voices (14M/11F), ElevenLabs 22 (14M/8F)
- Voice pools auto-switch when TTS provider changes
- Add cover art locally, update cache-busted image refs
- Add "More from Luke" footer links (MMG, prints, YouTube)
- Ad channel configurable in settings UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 01:34:30 -07:00
luke f654a5cbb1 Deep caller personality: named people, memories, vehicles, opinions, arcs
- Named relationships (20M/20F): "my buddy Ray", "my wife Linda" — not generic
- Relationship status with detail: "married 15 years, second marriage"
- Vehicle they drive: rural southwest flavor (F-150s, Tacomas, old Broncos)
- What they were doing before calling: grounds call in a physical moment
- Specific memory/story to reference: flash floods, poker wins, desert nights
- Food/drink right now: Tecate on the porch, third cup of coffee
- Strong random opinions: speed limits, green chile, desert philosophy
- Contradictions/secrets: tough guy who cries at TV, reads physics at work
- Verbal fingerprints: 2 specific phrases per caller
- Emotional arcs: mood shifts during the call
- Show relationship: first-timer, regular, skeptic, reactive
- Late-night reasons: why they're awake
- Topic drift tendencies for some callers
- Regional speech patterns in prompt (over in, down the road, out here)
- Opening line variety based on personality
- Local town news enrichment via SearXNG
- Ad channel now configurable in settings UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 01:01:32 -07:00
luke 6447edd0ae Add architecture diagram and system documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 00:36:17 -07:00
luke 79e6bc79be Add topic callers, town knowledge, dynamic response lengths
- 30% of callers now call about topics (prestige TV, science, poker,
  astrophotography, physics, tech, US news) instead of personal problems
- 86 curated interests weighted toward shows like Severance, Breaking Bad,
  The Wire, LOST, Westworld, etc. Removed reality TV/celebrity gossip
- 32-town knowledge base with real facts so callers don't invent landmarks
- Smart topic detection for news enrichment (keyword->search query mapping)
- Enrichment now summarizes articles naturally via LLM instead of quoting headlines
- Prompt rewrite for varied response lengths and no rehashing
- Extra weight for Animas and Lordsburg callers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 00:14:38 -07:00
luke 9452b07c5c Ads play once on channel 11, separate from music
- Add dedicated ad playback system (no loop, own channel)
- Ad channel defaults to 11, saved/loaded with audio settings
- Separate play_ad/stop_ad methods and API endpoints
- Frontend stop button now calls /api/ads/stop instead of stopMusic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:35:07 -07:00
luke aa3899b1fc Harden LLM: model fallback chain, reuse client, remove fighting timeouts
- Primary model gets 15s, then auto-falls back through gemini-flash,
  gpt-4o-mini, llama-3.1-8b (10s each)
- Always returns a response — canned in-character line as last resort
- Reuse httpx client instead of creating new one per request
- Remove asyncio.timeout wrappers that were killing requests before
  the LLM service could try fallbacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:07:39 -07:00
luke 73129374f4 Bake news context into caller backgrounds at pickup time
Instead of injecting research into every LLM call (which bloated prompts
and caused timeouts), do one quick SearXNG search when a caller is picked
up and add a relevant headline to their background. 3s timeout — if search
is slow, caller just doesn't reference news. Zero impact on live conversation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:47:46 -07:00
luke 164f8fbc6e Remove news/research from caller prompts — was causing timeouts
Research results were bloating the system prompt, making LLM calls
slower and hitting the 20s timeout. Callers don't need news awareness
to have good conversations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:44:22 -07:00
luke e45ba2617a Switch news service from Google News to local SearXNG
- Use local SearXNG at localhost:8888 instead of Google News RSS
- No more 302 redirects or blocked requests — local is fast and reliable
- 5s timeout on all SearXNG requests
- Removed async locks (no contention needed for local service)
- Re-enabled research and headlines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:34:45 -07:00
luke c03f46ea96 Disable news research — was breaking calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:32:50 -07:00
luke 69b7078142 Fix research hanging: add timeouts, fix keyword extraction, cache failures
- Google News RSS returns 302: add follow_redirects and User-Agent header
- Cache failed headline fetches for 5min so they don't retry every call
- Add 8s timeout on background research tasks
- Fix keyword extraction: skip short texts, require 2+ proper nouns (not names),
  increase min word length to 6, add radio show filler to stop words
- Stops garbage searches like "Megan welcome" and "sounds thats youre"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:25:31 -07:00
luke b3fb3b1127 Fix AI caller hanging on 'thinking...' indefinitely
- Add 30s timeout to all frontend fetch calls (safeFetch)
- Add 20s asyncio.timeout around lock+LLM in chat, ai-respond, auto-respond
- Reduce OpenRouter timeout from 60s to 25s
- Reduce Inworld TTS timeout from 60s to 25s
- Return graceful fallback responses on timeout instead of hanging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 21:16:15 -07:00
luke cac80a4b52 Localize callers to NM bootheel area
80% from Lordsburg/Animas/Portal/Playas/Deming/Silver City area,
20% out-of-staters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 20:46:51 -07:00
luke e30d4c8856 Add ads system, diversify callers, update website descriptions
- Add ads playback system with backend endpoints and frontend UI
- Diversify AI callers: randomize voices per session, expand jobs/problems/interests/quirks/locations
- Update website tagline and descriptions to "biologically questionable organisms"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 20:38:25 -07:00
luke eafcf27beb Add SEO, structured data, sitemap, and llms.txt
- Expanded meta description with keywords
- Canonical URL
- JSON-LD PodcastSeries structured data
- RSS alternate link for feed discovery
- robots.txt allowing all crawlers
- sitemap.xml
- llms.txt for LLM indexing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:07:45 -07:00
125 changed files with 32706 additions and 753 deletions
+48
View File
@@ -0,0 +1,48 @@
# API Keys
OPENROUTER_API_KEY=
ELEVENLABS_API_KEY=
INWORLD_API_KEY=
OPENAI_API_KEY=
# SignalWire (real callers)
SIGNALWIRE_PROJECT_ID=
SIGNALWIRE_SPACE=
SIGNALWIRE_TOKEN=
SIGNALWIRE_PHONE=
SIGNALWIRE_STREAM_URL=
# Social media
TWITTER_API_KEY=
TWITTER_API_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
CLIENT_SECRET_ID=
CLIENT_SECRET=
POSTIZ_URL=
POSTIZ_API_KEY=
BSKY_APP_PASSWORD=
# Castopod
CASTOPOD_USERNAME=admin
CASTOPOD_PASSWORD=
CASTOPOD_DB_PASS=
# Postiz publishing
POSTIZ_JWT_SECRET=
POSTIZ_USER_ID=
POSTIZ_INTEGRATIONS={}
# BunnyCDN
BUNNY_STORAGE_KEY=
BUNNY_ACCOUNT_KEY=
# Monitoring
HEARTBEAT_URL=
# Google AI
GOOGLE_API_KEY=
# Email (IMAP)
SUBMISSIONS_IMAP_HOST=
SUBMISSIONS_IMAP_USER=
SUBMISSIONS_IMAP_PASS=
+1
View File
@@ -0,0 +1 @@
2c7fcdb5aecbb0d3bf039abab9e723d62bc8fe1a
+7
View File
@@ -50,5 +50,12 @@ voices-v1.0.bin
# Reference voices for TTS
ref_audio/
# YouTube OAuth credentials
youtube_client_secrets.json
youtube_token.json
# Clip upload history (local)
upload-history.json
# Claude settings (local)
.claude/
+44 -4
View File
@@ -18,15 +18,14 @@
## Castopod (Podcast Publishing)
- **URL**: https://podcast.macneilmediagroup.com
- **Podcast handle**: `@LukeAtTheRoost`
- **API Auth**: Basic auth (admin/podcast2026api)
- **API Auth**: Basic auth (credentials in .env: CASTOPOD_USERNAME, CASTOPOD_PASSWORD)
- **Container**: `castopod-castopod-1`
- **Database**: `castopod-mariadb-1` (user: castopod, db: castopod)
## Running the App
```bash
# Start backend
cd /Users/lukemacneil/ai-podcast
python -m uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
# Start backend — ALWAYS use --reload-dir to avoid CPU thrashing from file watchers
python -m uvicorn backend.main:app --reload --reload-dir backend --host 0.0.0.0 --port 8000
# Or use run.sh
./run.sh
@@ -42,3 +41,44 @@ Required in `.env`:
- OPENROUTER_API_KEY
- ELEVENLABS_API_KEY (optional)
- INWORLD_API_KEY (for Inworld TTS)
## Post-Production Pipeline (added Feb 2026)
- **Branch**: `feature/real-callers` — all current work is here, pushed to gitea
- **Stem Recorder** (`backend/services/stem_recorder.py`): Records 5 WAV stems (host, caller, music, sfx, ads) during live shows. Uses lock-free deque architecture — audio callbacks just append to deques, a background writer thread drains to disk. `write()` for continuous streams (host mic, music, ads), `write_sporadic()` for burst sources (caller TTS, SFX) with time-aligned silence padding.
- **Audio hooks** in `backend/services/audio.py`: 7 tap points guarded by `if self.stem_recorder:`. Persistent mic stream (`start_stem_mic`/`stop_stem_mic`) runs during recording to capture host voice continuously, not just during push-to-talk.
- **API endpoints**: `POST /api/recording/start`, `POST /api/recording/stop` (auto-runs postprod in background thread), `POST /api/recording/process`
- **Frontend**: REC button in header with red pulse animation when recording
- **Post-prod script** (`postprod.py`): 6-step pipeline — load stems → gap removal → voice compression (ffmpeg acompressor) → music ducking → stereo mix → EBU R128 loudness normalization to -16 LUFS. All steps skippable via CLI flags.
- **Known issues resolved**: Lock-free recorder (old version used threading.Lock in audio callbacks causing crashes), scipy.signal.resample replaced with nearest-neighbor (was producing artifacts on small chunks), sys import bug in auto-postprod, host mic not captured without persistent stream
## LLM Settings
- `_pick_response_budget()` in main.py controls caller dialog token limits (150-450 tokens). MiniMax respects limits strictly — if responses seem short, check these values.
- Default max_tokens in llm.py is 300 (for non-caller uses)
- Grok (`x-ai/grok-4-fast`) works well for natural dialog; MiniMax tends toward terse responses
## Website
- **Domain**: lukeattheroost.com (behind Cloudflare)
- **Analytics**: Cloudflare Web Analytics (enable in Cloudflare dashboard, no code changes needed)
- **Deploy**: `npx wrangler pages deploy website/ --project-name=lukeattheroost --branch=main`
## Git Push
- If `mmgnas` times out, use the 10g hostname:
```bash
GIT_SSH_COMMAND="ssh -o HostName=mmgnas-10g -p 2222 -i ~/.ssh/gitea_mmgnas" git push origin main
```
## Hetzner VPS
- **IP**: `46.225.164.41`
- **SSH**: `ssh root@46.225.164.41` (uses default key `~/.ssh/id_rsa`)
- **Specs**: 2 CPU, 4GB RAM, 38GB disk (~33GB free)
- **Mail**: `docker-mailserver` at `/opt/mailserver/`
- **Manage accounts**: `docker exec mailserver setup email add/del/list`
- **Available for future services** — has headroom for lightweight containers. Not suitable for storage-heavy services (e.g. Castopod with daily episodes) without a disk upgrade or attached volume.
## Podcast Workflow
- Publishing pipeline: episodes go through Castopod, CDN, website, YouTube, and social
- Always check Python venv is active and packages are installed before running publish scripts
- Episode numbering must be verified against existing episodes
## Episodes Published
- Episode 6 published 2026-02-08 (podcast6.mp3, ~31 min)
+8 -1
View File
@@ -1,10 +1,17 @@
{
"input_device": 13,
"input_device_name": "Babyface Pro (70793771)",
"input_channel": 1,
"output_device": 13,
"output_device": 12,
"output_device_name": "Radio Voice Mic",
"caller_channel": 3,
"live_caller_channel": 9,
"music_channel": 5,
"sfx_channel": 7,
"ad_channel": 11,
"ident_channel": 15,
"monitor_device": null,
"monitor_device_name": null,
"monitor_channel": 1,
"phone_filter": false
}
+8 -1
View File
@@ -22,9 +22,14 @@ class Settings(BaseSettings):
signalwire_phone: str = os.getenv("SIGNALWIRE_PHONE", "")
signalwire_stream_url: str = os.getenv("SIGNALWIRE_STREAM_URL", "")
# Email (IMAP)
submissions_imap_host: str = os.getenv("SUBMISSIONS_IMAP_HOST", "")
submissions_imap_user: str = os.getenv("SUBMISSIONS_IMAP_USER", "")
submissions_imap_pass: str = os.getenv("SUBMISSIONS_IMAP_PASS", "")
# LLM Settings
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_host: str = "http://localhost:11434"
@@ -38,6 +43,8 @@ class Settings(BaseSettings):
base_dir: Path = Path(__file__).parent.parent
sounds_dir: Path = base_dir / "sounds"
music_dir: Path = base_dir / "music"
ads_dir: Path = base_dir / "ads"
idents_dir: Path = base_dir / "idents"
sessions_dir: Path = base_dir / "sessions"
class Config:
+5307 -265
View File
File diff suppressed because it is too large Load Diff
+657 -32
View File
@@ -10,25 +10,83 @@ from typing import Optional, Callable
import wave
import time
# Settings file path
SETTINGS_FILE = Path(__file__).parent.parent.parent / "audio_settings.json"
# REAPER state file for dialog region markers
REAPER_STATE_FILE = "/tmp/reaper_state.txt"
def _write_reaper_state(state: str):
"""Write state to file. Uses a thread so it's safe from audio callbacks."""
def _write():
try:
with open(REAPER_STATE_FILE, "w") as f:
f.write(state)
except OSError:
pass
threading.Thread(target=_write, daemon=True).start()
class AudioService:
"""Manages audio I/O with multi-channel support for Loopback routing"""
@staticmethod
def _find_device_by_name(name: str) -> Optional[int]:
"""Find a device index by name substring match. Returns None if not found."""
if not name:
return None
devices = sd.query_devices()
# Exact match first
for i, d in enumerate(devices):
if d["name"] == name:
return i
# Substring match
for i, d in enumerate(devices):
if name in d["name"]:
return i
return None
@staticmethod
def _get_device_name(device_id: Optional[int]) -> Optional[str]:
"""Get the name of a device by index."""
if device_id is None:
return None
try:
return sd.query_devices(device_id)["name"]
except Exception:
return None
def __init__(self):
# Device configuration
self.input_device: Optional[int] = None
self.input_device: Optional[int] = 13 # Radio Voice Mic (loopback input)
self.input_channel: int = 1 # 1-indexed channel
self.output_device: Optional[int] = None # Single output device (multi-channel)
self.caller_channel: int = 1 # Channel for caller TTS
self.output_device: Optional[int] = 12 # Radio Voice Mic (loopback output)
self.caller_channel: int = 3 # Channel for caller TTS
self.live_caller_channel: int = 9 # Channel for live caller audio
self.music_channel: int = 2 # Channel for music
self.music_channel: int = 5 # Channel for music
self.sfx_channel: int = 3 # Channel for SFX
self.ad_channel: int = 11 # Channel for ads
self.ident_channel: int = 15 # Channel for idents (stereo: ch 15+16)
self.monitor_device: Optional[int] = 14 # Babyface Pro (headphone monitoring)
self.monitor_channel: int = 1 # Channel for mic monitoring on monitor device
self.phone_filter: bool = False # Phone filter on caller voices
# Ad playback state
self._ad_stream: Optional[sd.OutputStream] = None
self._ad_data: Optional[np.ndarray] = None
self._ad_resampled: Optional[np.ndarray] = None
self._ad_position: int = 0
self._ad_playing: bool = False
# Ident playback state
self._ident_stream: Optional[sd.OutputStream] = None
self._ident_data: Optional[np.ndarray] = None
self._ident_resampled: Optional[np.ndarray] = None
self._ident_position: int = 0
self._ident_playing: bool = False
# Recording state
self._recording = False
self._record_thread: Optional[threading.Thread] = None
@@ -45,6 +103,14 @@ class AudioService:
self._music_volume: float = 0.3
self._music_loop: bool = True
# Music crossfade state
self._crossfade_active: bool = False
self._crossfade_old_data: Optional[np.ndarray] = None
self._crossfade_old_position: int = 0
self._crossfade_progress: float = 0.0
self._crossfade_samples: int = 0
self._crossfade_step: float = 0.0
# Caller playback state
self._caller_stop_event = threading.Event()
self._caller_thread: Optional[threading.Thread] = None
@@ -62,38 +128,72 @@ class AudioService:
self.input_sample_rate = 16000 # For Whisper
self.output_sample_rate = 24000 # For TTS
# Mic monitor (input → monitor device passthrough)
self._monitor_stream: Optional[sd.OutputStream] = None
self._monitor_write: Optional[Callable] = None
# Stem recording (opt-in, attached via API)
self.stem_recorder = None
self._stem_mic_stream: Optional[sd.InputStream] = None
# Load saved settings
self._load_settings()
def _resolve_device(self, data: dict, key: str) -> Optional[int]:
"""Resolve a device from settings: try name first, fall back to index."""
name_key = f"{key}_name"
name = data.get(name_key)
if name:
resolved = self._find_device_by_name(name)
if resolved is not None:
idx = data.get(key)
if idx is not None and resolved != idx:
print(f"[Audio] Device '{name}' moved: {idx} -> {resolved}")
return resolved
else:
print(f"[Audio] Warning: device '{name}' not found, falling back to index {data.get(key)}")
return data.get(key)
def _load_settings(self):
"""Load settings from disk"""
"""Load settings from disk, resolving device names to current indices"""
if SETTINGS_FILE.exists():
try:
with open(SETTINGS_FILE) as f:
data = json.load(f)
self.input_device = data.get("input_device")
self.input_device = self._resolve_device(data, "input_device")
self.input_channel = data.get("input_channel", 1)
self.output_device = data.get("output_device")
self.output_device = self._resolve_device(data, "output_device")
self.caller_channel = data.get("caller_channel", 1)
self.live_caller_channel = data.get("live_caller_channel", 4)
self.music_channel = data.get("music_channel", 2)
self.sfx_channel = data.get("sfx_channel", 3)
self.ad_channel = data.get("ad_channel", 11)
self.ident_channel = data.get("ident_channel", 15)
self.monitor_device = self._resolve_device(data, "monitor_device")
self.monitor_channel = data.get("monitor_channel", 1)
self.phone_filter = data.get("phone_filter", False)
print(f"Loaded audio settings: output={self.output_device}, channels={self.caller_channel}/{self.live_caller_channel}/{self.music_channel}/{self.sfx_channel}, phone_filter={self.phone_filter}")
print(f"Loaded audio settings: input={self.input_device} ({self._get_device_name(self.input_device)}), output={self.output_device} ({self._get_device_name(self.output_device)}), monitor={self.monitor_device}, phone_filter={self.phone_filter}")
except Exception as e:
print(f"Failed to load audio settings: {e}")
def _save_settings(self):
"""Save settings to disk"""
"""Save settings to disk with device names for stable resolution"""
try:
data = {
"input_device": self.input_device,
"input_device_name": self._get_device_name(self.input_device),
"input_channel": self.input_channel,
"output_device": self.output_device,
"output_device_name": self._get_device_name(self.output_device),
"caller_channel": self.caller_channel,
"live_caller_channel": self.live_caller_channel,
"music_channel": self.music_channel,
"sfx_channel": self.sfx_channel,
"ad_channel": self.ad_channel,
"ident_channel": self.ident_channel,
"monitor_device": self.monitor_device,
"monitor_device_name": self._get_device_name(self.monitor_device),
"monitor_channel": self.monitor_channel,
"phone_filter": self.phone_filter,
}
with open(SETTINGS_FILE, "w") as f:
@@ -125,6 +225,10 @@ class AudioService:
live_caller_channel: Optional[int] = None,
music_channel: Optional[int] = None,
sfx_channel: Optional[int] = None,
ad_channel: Optional[int] = None,
ident_channel: Optional[int] = None,
monitor_device: Optional[int] = None,
monitor_channel: Optional[int] = None,
phone_filter: Optional[bool] = None
):
"""Configure audio devices and channels"""
@@ -142,6 +246,14 @@ class AudioService:
self.music_channel = music_channel
if sfx_channel is not None:
self.sfx_channel = sfx_channel
if ad_channel is not None:
self.ad_channel = ad_channel
if ident_channel is not None:
self.ident_channel = ident_channel
if monitor_device is not None:
self.monitor_device = monitor_device
if monitor_channel is not None:
self.monitor_channel = monitor_channel
if phone_filter is not None:
self.phone_filter = phone_filter
@@ -158,6 +270,10 @@ class AudioService:
"live_caller_channel": self.live_caller_channel,
"music_channel": self.music_channel,
"sfx_channel": self.sfx_channel,
"ad_channel": self.ad_channel,
"ident_channel": self.ident_channel,
"monitor_device": self.monitor_device,
"monitor_channel": self.monitor_channel,
"phone_filter": self.phone_filter,
}
@@ -257,6 +373,8 @@ class AudioService:
stream_ready.set()
if self._recording:
self._recorded_audio.append(indata[:, record_channel].copy())
if self.stem_recorder:
self.stem_recorder.write("host", indata[:, record_channel].copy(), device_sr)
print(f"Recording: opening stream on device {self.input_device} ch {self.input_channel} @ {device_sr}Hz ({max_channels} ch)")
@@ -352,6 +470,9 @@ class AudioService:
while pos < len(multi_ch) and not self._caller_stop_event.is_set():
end = min(pos + chunk_size, len(multi_ch))
stream.write(multi_ch[pos:end])
# Record each chunk as it plays so hangups cut the stem too
if self.stem_recorder:
self.stem_recorder.write_sporadic("caller", audio[pos:end].copy(), device_sr)
pos = end
if self._caller_stop_event.is_set():
@@ -430,7 +551,7 @@ class AudioService:
self._live_caller_write = write_audio
self._live_caller_stream = sd.OutputStream(
self._live_caller_stream = self._open_output_stream(
device=self.output_device,
samplerate=device_sr,
channels=num_channels,
@@ -438,16 +559,15 @@ class AudioService:
callback=callback,
blocksize=1024,
)
self._live_caller_stream.start()
print(f"[Audio] Live caller stream started on ch {self.live_caller_channel} @ {device_sr}Hz (prebuffer {prebuffer_samples} samples)")
def _stop_live_caller_stream(self):
"""Stop persistent live caller output stream"""
if self._live_caller_stream:
self._live_caller_stream.stop()
self._live_caller_stream.close()
stream = self._live_caller_stream
self._live_caller_stream = None
self._live_caller_write = None
self._close_stream(stream)
print("[Audio] Live caller stream stopped")
def route_real_caller_audio(self, pcm_data: bytes, sample_rate: int):
@@ -469,6 +589,10 @@ class AudioService:
indices = np.clip(indices, 0, len(audio) - 1)
audio = audio[indices]
# Stem recording: live caller
if self.stem_recorder:
self.stem_recorder.write_sporadic("caller", audio.copy(), device_sr)
if self._live_caller_write:
self._live_caller_write(audio)
@@ -479,10 +603,20 @@ class AudioService:
def start_host_stream(self, send_callback: Callable):
"""Start continuous host mic capture for streaming to real callers"""
if self._host_stream is not None:
self._host_send_callback = send_callback
return
if self.input_device is None:
print("[Audio] No input device configured for host streaming")
return
# Close stem_mic if active — this stream's callback handles stem recording too
if self._stem_mic_stream is not None:
stream = self._stem_mic_stream
self._stem_mic_stream = None
self._close_stream(stream)
print("[Audio] Closed stem_mic (host stream takes over)")
self._host_send_callback = send_callback
def _start():
@@ -497,11 +631,22 @@ class AudioService:
host_accum_samples = [0]
send_threshold = 1600 # 100ms at 16kHz
# Start mic monitor if monitor device is configured
self._start_monitor(device_sr)
def callback(indata, frames, time_info, status):
# Capture for push-to-talk recording if active
if self._recording and self._recorded_audio is not None:
self._recorded_audio.append(indata[:, record_channel].copy())
# Stem recording: host mic
if self.stem_recorder:
self.stem_recorder.write("host", indata[:, record_channel].copy(), device_sr)
# Mic monitor: send to headphone device
if self._monitor_write:
self._monitor_write(indata[:, record_channel].copy())
if not self._host_send_callback:
return
mono = indata[:, record_channel]
@@ -537,13 +682,89 @@ class AudioService:
def stop_host_stream(self):
"""Stop host mic streaming and live caller output"""
if self._host_stream:
self._host_stream.stop()
self._host_stream.close()
stream = self._host_stream
self._host_stream = None
self._host_send_callback = None
self._close_stream(stream)
print("[Audio] Host mic streaming stopped")
self._stop_monitor()
self._stop_live_caller_stream()
# --- Mic Monitor (input → headphone device) ---
def _start_monitor(self, input_sr: int):
"""Start mic monitor stream that routes input to monitor device"""
if self._monitor_stream is not None:
return
if self.monitor_device is None:
return
device_info = sd.query_devices(self.monitor_device)
num_channels = device_info['max_output_channels']
device_sr = int(device_info['default_samplerate'])
channel_idx = min(self.monitor_channel, num_channels) - 1
# Ring buffer for cross-device routing
ring_size = int(device_sr * 2)
ring = np.zeros(ring_size, dtype=np.float32)
state = {"write_pos": 0, "read_pos": 0, "avail": 0}
# Precompute resample ratio (input device sr → monitor device sr)
resample_ratio = device_sr / input_sr
def write_audio(data):
# Resample if sample rates differ
if abs(resample_ratio - 1.0) > 0.01:
n_out = int(len(data) * resample_ratio)
indices = np.linspace(0, len(data) - 1, n_out).astype(int)
data = data[indices]
n = len(data)
wp = state["write_pos"]
if wp + n <= ring_size:
ring[wp:wp + n] = data
else:
first = ring_size - wp
ring[wp:] = data[:first]
ring[:n - first] = data[first:]
state["write_pos"] = (wp + n) % ring_size
state["avail"] += n
def callback(outdata, frames, time_info, status):
outdata.fill(0)
avail = state["avail"]
if avail < frames:
return
rp = state["read_pos"]
if rp + frames <= ring_size:
outdata[:frames, channel_idx] = ring[rp:rp + frames]
else:
first = ring_size - rp
outdata[:first, channel_idx] = ring[rp:]
outdata[first:frames, channel_idx] = ring[:frames - first]
state["read_pos"] = (rp + frames) % ring_size
state["avail"] -= frames
self._monitor_write = write_audio
self._monitor_stream = sd.OutputStream(
device=self.monitor_device,
samplerate=device_sr,
channels=num_channels,
dtype=np.float32,
blocksize=1024,
callback=callback,
)
self._monitor_stream.start()
print(f"[Audio] Mic monitor started (device {self.monitor_device} ch {self.monitor_channel} @ {device_sr}Hz)")
def _stop_monitor(self):
"""Stop mic monitor stream"""
if self._monitor_stream:
stream = self._monitor_stream
self._monitor_stream = None
self._monitor_write = None
self._close_stream(stream)
print("[Audio] Mic monitor stopped")
# --- Music Playback ---
def load_music(self, file_path: str) -> bool:
@@ -564,6 +785,55 @@ class AudioService:
print(f"Failed to load music: {e}")
return False
def crossfade_to(self, file_path: str, duration: float = 3.0):
"""Crossfade from current music track to a new one"""
import librosa
if not self._music_playing or self._music_resampled is None:
if self.load_music(file_path):
self.play_music()
return
# Load the new track
path = Path(file_path)
if not path.exists():
print(f"Music file not found: {file_path}")
return
try:
audio, sr = librosa.load(str(path), sr=self.output_sample_rate, mono=True)
new_data = audio.astype(np.float32)
except Exception as e:
print(f"Failed to load music for crossfade: {e}")
return
# Get device sample rate for resampling
if self.output_device is not None:
device_info = sd.query_devices(self.output_device)
device_sr = int(device_info['default_samplerate'])
else:
device_sr = self.output_sample_rate
if self.output_sample_rate != device_sr:
new_resampled = librosa.resample(new_data, orig_sr=self.output_sample_rate, target_sr=device_sr)
else:
new_resampled = new_data.copy()
# Swap: current becomes old, new becomes current
self._crossfade_old_data = self._music_resampled
self._crossfade_old_position = self._music_position
self._music_resampled = new_resampled
self._music_data = new_data
self._music_position = 0
# Configure crossfade timing
self._crossfade_samples = int(device_sr * duration)
self._crossfade_progress = 0.0
self._crossfade_step = 1.0 / self._crossfade_samples if self._crossfade_samples > 0 else 1.0
self._crossfade_active = True
print(f"Crossfading to {path.name} over {duration}s")
def play_music(self):
"""Start music playback to specific channel"""
import librosa
@@ -611,27 +881,63 @@ class AudioService:
if not self._music_playing or self._music_resampled is None:
return
# Read new track samples
end_pos = self._music_position + frames
if end_pos <= len(self._music_resampled):
outdata[:, channel_idx] = self._music_resampled[self._music_position:end_pos] * self._music_volume
new_samples = self._music_resampled[self._music_position:end_pos].copy()
self._music_position = end_pos
else:
remaining = len(self._music_resampled) - self._music_position
new_samples = np.zeros(frames, dtype=np.float32)
if remaining > 0:
outdata[:remaining, channel_idx] = self._music_resampled[self._music_position:] * self._music_volume
new_samples[:remaining] = self._music_resampled[self._music_position:]
if self._music_loop:
self._music_position = 0
wrap_frames = frames - remaining
if wrap_frames > 0:
outdata[remaining:, channel_idx] = self._music_resampled[:wrap_frames] * self._music_volume
new_samples[remaining:] = self._music_resampled[:wrap_frames]
self._music_position = wrap_frames
else:
self._music_playing = False
self._music_position = len(self._music_resampled)
if remaining <= 0:
self._music_playing = False
if self._crossfade_active and self._crossfade_old_data is not None:
# Read old track samples
old_end = self._crossfade_old_position + frames
if old_end <= len(self._crossfade_old_data):
old_samples = self._crossfade_old_data[self._crossfade_old_position:old_end]
self._crossfade_old_position = old_end
else:
old_remaining = len(self._crossfade_old_data) - self._crossfade_old_position
old_samples = np.zeros(frames, dtype=np.float32)
if old_remaining > 0:
old_samples[:old_remaining] = self._crossfade_old_data[self._crossfade_old_position:]
self._crossfade_old_position = len(self._crossfade_old_data)
# Compute fade curves for this chunk
start_progress = self._crossfade_progress
end_progress = min(1.0, start_progress + self._crossfade_step * frames)
fade_in = np.linspace(start_progress, end_progress, frames, dtype=np.float32)
fade_out = 1.0 - fade_in
mono_out = (old_samples * fade_out + new_samples * fade_in) * self._music_volume
outdata[:, channel_idx] = mono_out
if self.stem_recorder:
self.stem_recorder.write_sporadic("music", mono_out.copy(), device_sr)
self._crossfade_progress = end_progress
if self._crossfade_progress >= 1.0:
self._crossfade_active = False
self._crossfade_old_data = None
print("Crossfade complete")
else:
mono_out = new_samples * self._music_volume
outdata[:, channel_idx] = mono_out
if self.stem_recorder:
self.stem_recorder.write_sporadic("music", mono_out.copy(), device_sr)
try:
self._music_stream = sd.OutputStream(
self._music_stream = self._open_output_stream(
device=device,
channels=num_channels,
samplerate=device_sr,
@@ -639,21 +945,287 @@ class AudioService:
callback=callback,
blocksize=2048
)
self._music_stream.start()
print(f"Music playback started on ch {self.music_channel} @ {device_sr}Hz")
except Exception as e:
print(f"Music playback error: {e}")
self._music_playing = False
def stop_music(self):
"""Stop music playback"""
self._music_playing = False
if self._music_stream:
self._music_stream.stop()
self._music_stream.close()
def _refresh_devices(self):
"""Re-initialize PortAudio to pick up device changes, then re-resolve settings."""
try:
sd._terminate()
sd._initialize()
print("[Audio] PortAudio re-initialized")
self._load_settings()
except Exception as e:
print(f"[Audio] PortAudio refresh failed: {e}")
def _open_output_stream(self, **kwargs) -> sd.OutputStream:
"""Open an OutputStream with one retry after refreshing PortAudio on failure."""
try:
stream = sd.OutputStream(**kwargs)
stream.start()
return stream
except Exception as first_err:
print(f"[Audio] Stream open failed ({first_err}), refreshing devices...")
self._refresh_devices()
# Update device/channel info from refreshed settings
if kwargs.get("device") == self.output_device or "device" in kwargs:
device_info = sd.query_devices(self.output_device)
kwargs["device"] = self.output_device
kwargs["channels"] = device_info["max_output_channels"]
kwargs["samplerate"] = int(device_info["default_samplerate"])
stream = sd.OutputStream(**kwargs)
stream.start()
return stream
def _close_stream(self, stream):
"""Safely close a sounddevice stream, ignoring double-close errors"""
if stream is None:
return
try:
stream.stop()
except Exception:
pass
try:
stream.close()
except Exception:
pass
def stop_music(self, fade_duration: float = 2.0):
"""Stop music playback with fade out"""
if not self._music_playing or not self._music_stream:
self._music_playing = False
stream = self._music_stream
self._music_stream = None
self._music_position = 0
print("Music stopped")
self._close_stream(stream)
self._music_position = 0
return
if fade_duration <= 0:
self._music_playing = False
stream = self._music_stream
self._music_stream = None
self._close_stream(stream)
self._music_position = 0
print("Music stopped")
return
import threading
original_volume = self._music_volume
steps = 20
step_time = fade_duration / steps
# Capture stream reference locally so the fade thread closes THIS stream,
# not whatever self._music_stream points to later
fade_stream = self._music_stream
self._music_stream = None
def _fade():
for i in range(steps):
if not self._music_playing:
break
self._music_volume = original_volume * (1 - (i + 1) / steps)
import time
time.sleep(step_time)
self._music_playing = False
self._close_stream(fade_stream)
self._music_position = 0
self._music_volume = original_volume
print("Music faded out and stopped")
threading.Thread(target=_fade, daemon=True).start()
def play_ad(self, file_path: str):
"""Load and play an ad file once (no loop) on the ad channel"""
import librosa
path = Path(file_path)
if not path.exists():
print(f"Ad file not found: {file_path}")
return
self.stop_ad()
self.stop_ident()
try:
audio, sr = librosa.load(str(path), sr=self.output_sample_rate, mono=True)
self._ad_data = audio.astype(np.float32)
except Exception as e:
print(f"Failed to load ad: {e}")
return
self._ad_playing = True
self._ad_position = 0
_write_reaper_state("ad")
if self.output_device is None:
num_channels = 2
device = None
device_sr = self.output_sample_rate
channel_idx = 0
else:
device_info = sd.query_devices(self.output_device)
num_channels = device_info['max_output_channels']
device_sr = int(device_info['default_samplerate'])
device = self.output_device
channel_idx = min(self.ad_channel, num_channels) - 1
if self.output_sample_rate != device_sr:
self._ad_resampled = librosa.resample(
self._ad_data, orig_sr=self.output_sample_rate, target_sr=device_sr
).astype(np.float32)
else:
self._ad_resampled = self._ad_data
def callback(outdata, frames, time_info, status):
outdata[:] = 0
if not self._ad_playing or self._ad_resampled is None:
return
remaining = len(self._ad_resampled) - self._ad_position
if remaining >= frames:
chunk = self._ad_resampled[self._ad_position:self._ad_position + frames]
outdata[:, channel_idx] = chunk
if self.stem_recorder:
self.stem_recorder.write_sporadic("ads", chunk.copy(), device_sr)
self._ad_position += frames
else:
if remaining > 0:
outdata[:remaining, channel_idx] = self._ad_resampled[self._ad_position:]
# Ad finished — no loop
self._ad_playing = False
_write_reaper_state("dialog")
try:
self._ad_stream = self._open_output_stream(
device=device,
channels=num_channels,
samplerate=device_sr,
dtype=np.float32,
callback=callback,
blocksize=2048
)
print(f"Ad playback started on ch {self.ad_channel} @ {device_sr}Hz")
except Exception as e:
print(f"Ad playback error: {e}")
self._ad_playing = False
def stop_ad(self):
"""Stop ad playback"""
was_playing = self._ad_playing
self._ad_playing = False
if was_playing:
_write_reaper_state("dialog")
if self._ad_stream:
stream = self._ad_stream
self._ad_stream = None
self._close_stream(stream)
self._ad_position = 0
def play_ident(self, file_path: str):
"""Load and play an ident file once (no loop) in stereo on ident_channel/ident_channel+1"""
import librosa
path = Path(file_path)
if not path.exists():
print(f"Ident file not found: {file_path}")
return
self.stop_ident()
self.stop_ad()
try:
audio, sr = librosa.load(str(path), sr=self.output_sample_rate, mono=False)
if audio.ndim == 1:
# Mono file — duplicate to stereo
audio = np.stack([audio, audio])
audio = audio.astype(np.float32) # shape: (2, samples)
self._ident_data = audio
except Exception as e:
print(f"Failed to load ident: {e}")
return
self._ident_playing = True
self._ident_position = 0
_write_reaper_state("ident")
print(f"Ident loaded: shape={self._ident_data.shape}, max={np.max(np.abs(self._ident_data)):.4f}")
if self.output_device is None:
num_channels = 2
device = None
device_sr = self.output_sample_rate
ch_l = 0
ch_r = 1
else:
device_info = sd.query_devices(self.output_device)
num_channels = device_info['max_output_channels']
device_sr = int(device_info['default_samplerate'])
device = self.output_device
ch_l = min(self.ident_channel, num_channels) - 1
ch_r = min(self.ident_channel + 1, num_channels) - 1
if self.output_sample_rate != device_sr:
self._ident_resampled = np.stack([
librosa.resample(self._ident_data[0], orig_sr=self.output_sample_rate, target_sr=device_sr),
librosa.resample(self._ident_data[1], orig_sr=self.output_sample_rate, target_sr=device_sr),
]).astype(np.float32)
else:
self._ident_resampled = self._ident_data
_cb_count = [0]
def callback(outdata, frames, time_info, status):
outdata[:] = 0
if not self._ident_playing or self._ident_resampled is None:
if _cb_count[0] == 0:
print(f"Ident callback: not playing (playing={self._ident_playing}, data={'yes' if self._ident_resampled is not None else 'no'})")
return
n_samples = self._ident_resampled.shape[1]
remaining = n_samples - self._ident_position
if remaining >= frames:
chunk_l = self._ident_resampled[0, self._ident_position:self._ident_position + frames]
chunk_r = self._ident_resampled[1, self._ident_position:self._ident_position + frames]
outdata[:, ch_l] = chunk_l
outdata[:, ch_r] = chunk_r
_cb_count[0] += 1
if _cb_count[0] == 1:
print(f"Ident callback delivering audio: ch_l={ch_l}, ch_r={ch_r}, max={max(np.max(np.abs(chunk_l)), np.max(np.abs(chunk_r))):.4f}")
if self.stem_recorder:
mono_mix = (chunk_l + chunk_r) * 0.5
self.stem_recorder.write_sporadic("idents", mono_mix.copy(), device_sr)
self._ident_position += frames
else:
if remaining > 0:
outdata[:remaining, ch_l] = self._ident_resampled[0, self._ident_position:]
outdata[:remaining, ch_r] = self._ident_resampled[1, self._ident_position:]
self._ident_playing = False
_write_reaper_state("dialog")
try:
self._ident_stream = self._open_output_stream(
device=device,
channels=num_channels,
samplerate=device_sr,
dtype=np.float32,
callback=callback,
blocksize=2048
)
print(f"Ident playback started on ch {ch_l+1}/{ch_r+1} (idx {ch_l}/{ch_r}) of {num_channels} channels @ {device_sr}Hz, device={device}")
except Exception as e:
print(f"Ident playback error: {e}")
self._ident_playing = False
def stop_ident(self):
"""Stop ident playback"""
was_playing = self._ident_playing
self._ident_playing = False
if was_playing:
_write_reaper_state("dialog")
if self._ident_stream:
stream = self._ident_stream
self._ident_stream = None
self._close_stream(stream)
self._ident_position = 0
def set_music_volume(self, volume: float):
"""Set music volume (0.0 to 1.0)"""
@@ -677,6 +1249,7 @@ class AudioService:
if self.output_device is None:
audio, sr = librosa.load(str(path), sr=None, mono=True)
audio = audio.astype(np.float32)
audio = self._apply_fade(audio, sr)
def play():
# Use a dedicated stream instead of sd.play()
@@ -689,8 +1262,13 @@ class AudioService:
channel_idx = min(self.sfx_channel, num_channels) - 1
audio, _ = librosa.load(str(path), sr=device_sr, mono=True)
audio = audio.astype(np.float32)
audio = self._apply_fade(audio, device_sr)
# Stem recording: sfx
if self.stem_recorder:
self.stem_recorder.write_sporadic("sfx", audio.copy(), device_sr)
multi_ch = np.zeros((len(audio), num_channels), dtype=np.float32)
multi_ch[:, channel_idx] = audio
@@ -709,6 +1287,53 @@ class AudioService:
except Exception as e:
print(f"SFX playback error: {e}")
# --- Stem Mic Capture ---
def start_stem_mic(self):
"""Start a persistent mic capture stream for stem recording.
Skips if _host_stream is already active (it writes to the host stem too)."""
if self._stem_mic_stream is not None:
return
if self._host_stream is not None:
print("[StemRecorder] Host stream already capturing mic, skipping stem_mic")
return
if self.input_device is None:
print("[StemRecorder] No input device configured, skipping host mic capture")
return
device_info = sd.query_devices(self.input_device)
max_channels = device_info['max_input_channels']
device_sr = int(device_info['default_samplerate'])
record_channel = min(self.input_channel, max_channels) - 1
self._start_monitor(device_sr)
def callback(indata, frames, time_info, status):
if self.stem_recorder:
self.stem_recorder.write("host", indata[:, record_channel].copy(), device_sr)
if self._monitor_write:
self._monitor_write(indata[:, record_channel].copy())
self._stem_mic_stream = sd.InputStream(
device=self.input_device,
channels=max_channels,
samplerate=device_sr,
dtype=np.float32,
blocksize=1024,
callback=callback,
)
self._stem_mic_stream.start()
print(f"[StemRecorder] Host mic capture started (device {self.input_device} ch {self.input_channel} @ {device_sr}Hz)")
def stop_stem_mic(self):
"""Stop the persistent stem mic capture."""
if self._stem_mic_stream:
stream = self._stem_mic_stream
self._stem_mic_stream = None
self._close_stream(stream)
print("[StemRecorder] Host mic capture stopped")
self._stop_monitor()
# Global instance
audio_service = AudioService()
+66 -12
View File
@@ -25,6 +25,7 @@ class CallerService:
self._stream_sids: dict[str, str] = {} # caller_id -> SignalWire streamSid
self._send_locks: dict[str, asyncio.Lock] = {} # per-caller send lock
self._streaming_tts: set[str] = set() # caller_ids currently receiving TTS
self._screening_state: dict[str, dict] = {} # caller_id -> screening conversation
def _get_send_lock(self, caller_id: str) -> asyncio.Lock:
if caller_id not in self._send_locks:
@@ -51,18 +52,6 @@ class CallerService:
self._queue = [c for c in self._queue if c["caller_id"] != caller_id]
print(f"[Caller] {caller_id} removed from queue")
def get_queue(self) -> list[dict]:
now = time.time()
with self._lock:
return [
{
"caller_id": c["caller_id"],
"phone": c["phone"],
"wait_time": int(now - c["queued_at"]),
}
for c in self._queue
]
def allocate_channel(self) -> int:
with self._lock:
ch = self.FIRST_REAL_CHANNEL
@@ -111,6 +100,7 @@ class CallerService:
self._call_sids.pop(caller_id, None)
self._stream_sids.pop(caller_id, None)
self._send_locks.pop(caller_id, None)
self._screening_state.pop(caller_id, None)
def reset(self):
with self._lock:
@@ -125,8 +115,72 @@ class CallerService:
self._stream_sids.clear()
self._send_locks.clear()
self._streaming_tts.clear()
self._screening_state.clear()
print("[Caller] Service reset")
# --- Screening ---
def start_screening(self, caller_id: str):
"""Initialize screening state for a queued caller"""
self._screening_state[caller_id] = {
"conversation": [],
"caller_name": None,
"topic": None,
"status": "screening", # screening, complete
"response_count": 0,
}
print(f"[Screening] Started for {caller_id}")
def get_screening_state(self, caller_id: str) -> Optional[dict]:
return self._screening_state.get(caller_id)
def update_screening(self, caller_id: str, caller_text: str = None,
screener_text: str = None, caller_name: str = None,
topic: str = None):
"""Update screening conversation and extracted info"""
state = self._screening_state.get(caller_id)
if not state:
return
if caller_text:
state["conversation"].append({"role": "caller", "content": caller_text})
state["response_count"] += 1
if screener_text:
state["conversation"].append({"role": "screener", "content": screener_text})
if caller_name:
state["caller_name"] = caller_name
if topic:
state["topic"] = topic
def end_screening(self, caller_id: str):
"""Mark screening as complete"""
state = self._screening_state.get(caller_id)
if state:
state["status"] = "complete"
print(f"[Screening] Complete for {caller_id}: name={state.get('caller_name')}, topic={state.get('topic')}")
def get_queue(self) -> list[dict]:
"""Get queue with screening info enrichment"""
now = time.time()
with self._lock:
result = []
for c in self._queue:
entry = {
"caller_id": c["caller_id"],
"phone": c["phone"],
"wait_time": int(now - c["queued_at"]),
}
screening = self._screening_state.get(c["caller_id"])
if screening:
entry["screening_status"] = screening["status"]
entry["caller_name"] = screening.get("caller_name")
entry["screening_summary"] = screening.get("topic")
else:
entry["screening_status"] = None
entry["caller_name"] = None
entry["screening_summary"] = None
result.append(entry)
return result
def register_websocket(self, caller_id: str, websocket):
"""Register a WebSocket for a caller"""
self._websockets[caller_id] = websocket
+92 -59
View File
@@ -7,14 +7,29 @@ from ..config import settings
# Available OpenRouter models
OPENROUTER_MODELS = [
# Default
"anthropic/claude-sonnet-4-5",
# Best for natural dialog
"x-ai/grok-4-fast",
"minimax/minimax-m2-her",
"mistralai/mistral-small-creative",
"deepseek/deepseek-v3.2",
# Other
"anthropic/claude-haiku-4.5",
"google/gemini-2.5-flash",
"openai/gpt-4o-mini",
"openai/gpt-4o",
# Legacy
"anthropic/claude-3-haiku",
"anthropic/claude-3.5-sonnet",
"google/gemini-flash-1.5",
"google/gemini-pro-1.5",
"meta-llama/llama-3.1-8b-instruct",
"mistralai/mistral-7b-instruct",
]
# Fast models to try as fallbacks (cheap, fast, good enough for conversation)
FALLBACK_MODELS = [
"mistralai/mistral-small-creative",
"google/gemini-2.5-flash",
"openai/gpt-4o-mini",
]
@@ -27,6 +42,13 @@ class LLMService:
self.ollama_model = settings.ollama_model
self.ollama_host = settings.ollama_host
self.tts_provider = settings.tts_provider
self._client: httpx.AsyncClient | None = None
@property
def client(self) -> httpx.AsyncClient:
if self._client is None or self._client.is_closed:
self._client = httpx.AsyncClient(timeout=10.0)
return self._client
def update_settings(
self,
@@ -47,7 +69,6 @@ class LLMService:
self.ollama_host = ollama_host
if tts_provider:
self.tts_provider = tts_provider
# Also update the global settings so TTS service picks it up
settings.tts_provider = tts_provider
async def get_ollama_models(self) -> list[str]:
@@ -71,7 +92,7 @@ class LLMService:
"ollama_host": self.ollama_host,
"tts_provider": self.tts_provider,
"available_openrouter_models": OPENROUTER_MODELS,
"available_ollama_models": [] # Fetched separately
"available_ollama_models": []
}
async def get_settings_async(self) -> dict:
@@ -90,61 +111,73 @@ class LLMService:
async def generate(
self,
messages: list[dict],
system_prompt: Optional[str] = None
system_prompt: Optional[str] = None,
max_tokens: Optional[int] = None
) -> str:
"""
Generate a response from the LLM.
Args:
messages: List of message dicts with 'role' and 'content'
system_prompt: Optional system prompt to prepend
Returns:
Generated text response
"""
if system_prompt:
messages = [{"role": "system", "content": system_prompt}] + messages
if self.provider == "openrouter":
return await self._call_openrouter(messages)
return await self._call_openrouter_with_fallback(messages, max_tokens=max_tokens)
else:
return await self._call_ollama(messages)
return await self._call_ollama(messages, max_tokens=max_tokens)
async def _call_openrouter(self, messages: list[dict]) -> str:
"""Call OpenRouter API with retry"""
for attempt in range(2): # Try twice
try:
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {settings.openrouter_api_key}",
"Content-Type": "application/json",
},
json={
"model": self.openrouter_model,
"messages": messages,
"max_tokens": 150,
},
)
response.raise_for_status()
data = response.json()
content = data["choices"][0]["message"]["content"]
if not content or not content.strip():
print(f"OpenRouter returned empty response")
return ""
return content
except (httpx.TimeoutException, httpx.ReadTimeout):
print(f"OpenRouter timeout (attempt {attempt + 1})")
if attempt == 0:
continue # Retry once
return "Uh, sorry, I lost you there for a second. What was that?"
except Exception as e:
print(f"OpenRouter error: {e}")
return "Yeah... I don't know, man."
return "Uh, hold on a sec..."
async def _call_openrouter_with_fallback(self, messages: list[dict], max_tokens: Optional[int] = None) -> str:
"""Try primary model, then fallback models. Always returns a response."""
async def _call_ollama(self, messages: list[dict]) -> str:
# Try primary model first
result = await self._call_openrouter_once(messages, self.openrouter_model, max_tokens=max_tokens)
if result is not None:
return result
# Try fallback models
for model in FALLBACK_MODELS:
if model == self.openrouter_model:
continue # Already tried
print(f"[LLM] Falling back to {model}...")
result = await self._call_openrouter_once(messages, model, timeout=8.0, max_tokens=max_tokens)
if result is not None:
return result
# Everything failed — return an in-character line so the show continues
print("[LLM] All models failed, using canned response")
return "Sorry, I totally blanked out for a second. What were you saying?"
async def _call_openrouter_once(self, messages: list[dict], model: str, timeout: float = 10.0, max_tokens: Optional[int] = None) -> str | None:
"""Single attempt to call OpenRouter. Returns None on failure (not a fallback string)."""
try:
response = await self.client.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {settings.openrouter_api_key}",
"Content-Type": "application/json",
},
json={
"model": model,
"messages": messages,
"max_tokens": max_tokens or 500,
"temperature": 0.65,
"top_p": 0.9,
"frequency_penalty": 0.3,
"presence_penalty": 0.15,
},
timeout=timeout,
)
response.raise_for_status()
data = response.json()
content = data["choices"][0]["message"]["content"]
if content and content.strip():
return content
print(f"[LLM] {model} returned empty response")
return None
except httpx.TimeoutException:
print(f"[LLM] {model} timed out ({timeout}s)")
return None
except Exception as e:
print(f"[LLM] {model} error: {e}")
return None
async def _call_ollama(self, messages: list[dict], max_tokens: Optional[int] = None) -> str:
"""Call Ollama API"""
try:
async with httpx.AsyncClient() as client:
@@ -155,11 +188,11 @@ class LLMService:
"messages": messages,
"stream": False,
"options": {
"num_predict": 100, # Allow complete thoughts
"temperature": 0.8, # Balanced creativity/coherence
"top_p": 0.9, # Focused word choices
"repeat_penalty": 1.3, # Avoid repetition
"top_k": 50, # Reasonable token variety
"num_predict": max_tokens or 100,
"temperature": 0.8,
"top_p": 0.9,
"repeat_penalty": 1.3,
"top_k": 50,
},
},
timeout=30.0
@@ -169,10 +202,10 @@ class LLMService:
return data["message"]["content"]
except httpx.TimeoutException:
print("Ollama timeout")
return "Uh, sorry, I lost you there for a second. What was that?"
return "Sorry, I totally blanked out for a second. What were you saying?"
except Exception as e:
print(f"Ollama error: {e}")
return "Yeah... I don't know, man."
return "Sorry, I totally blanked out for a second. What were you saying?"
# Global instance
+79 -65
View File
@@ -1,20 +1,21 @@
"""News service for current events awareness in AI callers"""
"""News service using local SearXNG for current events awareness in AI callers"""
import asyncio
import time
import re
from dataclasses import dataclass
from urllib.parse import quote_plus
from xml.etree import ElementTree
import httpx
SEARXNG_URL = "http://localhost:8888"
@dataclass
class NewsItem:
title: str
source: str
published: str
content: str = ""
class NewsService:
@@ -22,81 +23,74 @@ class NewsService:
self._client: httpx.AsyncClient | None = None
self._headlines_cache: list[NewsItem] = []
self._headlines_ts: float = 0
self._headlines_lock = asyncio.Lock()
self._search_cache: dict[str, tuple[float, list[NewsItem]]] = {}
self._search_lock = asyncio.Lock()
@property
def client(self) -> httpx.AsyncClient:
if self._client is None or self._client.is_closed:
self._client = httpx.AsyncClient(timeout=10.0)
self._client = httpx.AsyncClient(timeout=5.0)
return self._client
async def get_headlines(self) -> list[NewsItem]:
async with self._headlines_lock:
if self._headlines_cache and time.time() - self._headlines_ts < 1800:
return self._headlines_cache
# Cache for 30min
if self._headlines_cache and time.time() - self._headlines_ts < 1800:
return self._headlines_cache
try:
resp = await self.client.get("https://news.google.com/rss")
resp.raise_for_status()
items = self._parse_rss(resp.text, max_items=10)
self._headlines_cache = items
self._headlines_ts = time.time()
return items
except Exception as e:
print(f"[News] Headlines fetch failed: {e}")
if self._headlines_cache:
return self._headlines_cache
return []
try:
resp = await self.client.get(
f"{SEARXNG_URL}/search",
params={"q": "news", "format": "json", "categories": "news"},
)
resp.raise_for_status()
items = self._parse_searxng(resp.json(), max_items=10)
self._headlines_cache = items
self._headlines_ts = time.time()
return items
except Exception as e:
print(f"[News] Headlines fetch failed: {e}")
self._headlines_ts = time.time()
return self._headlines_cache
async def search_topic(self, query: str) -> list[NewsItem]:
cache_key = query.lower()
async with self._search_lock:
if cache_key in self._search_cache:
ts, items = self._search_cache[cache_key]
if time.time() - ts < 600:
return items
if cache_key in self._search_cache:
ts, items = self._search_cache[cache_key]
if time.time() - ts < 600:
return items
# Evict oldest when cache too large
if len(self._search_cache) > 50:
oldest_key = min(self._search_cache, key=lambda k: self._search_cache[k][0])
del self._search_cache[oldest_key]
# Evict oldest when cache too large
if len(self._search_cache) > 50:
oldest_key = min(self._search_cache, key=lambda k: self._search_cache[k][0])
del self._search_cache[oldest_key]
try:
encoded = quote_plus(query)
url = f"https://news.google.com/rss/search?q={encoded}&hl=en-US&gl=US&ceid=US:en"
resp = await self.client.get(url)
resp = await self.client.get(
f"{SEARXNG_URL}/search",
params={"q": query, "format": "json", "categories": "news"},
)
resp.raise_for_status()
items = self._parse_rss(resp.text, max_items=5)
async with self._search_lock:
self._search_cache[cache_key] = (time.time(), items)
items = self._parse_searxng(resp.json(), max_items=5)
self._search_cache[cache_key] = (time.time(), items)
return items
except Exception as e:
print(f"[News] Search failed for '{query}': {e}")
async with self._search_lock:
if cache_key in self._search_cache:
return self._search_cache[cache_key][1]
if cache_key in self._search_cache:
return self._search_cache[cache_key][1]
return []
def _parse_rss(self, xml_text: str, max_items: int = 10) -> list[NewsItem]:
def _parse_searxng(self, data: dict, max_items: int = 10) -> list[NewsItem]:
items = []
try:
root = ElementTree.fromstring(xml_text)
for item_el in root.iter("item"):
if len(items) >= max_items:
break
title = item_el.findtext("title", "").strip()
source_el = item_el.find("source")
source = source_el.text.strip() if source_el is not None and source_el.text else ""
published = item_el.findtext("pubDate", "").strip()
if title:
items.append(NewsItem(title=title, source=source, published=published))
except ElementTree.ParseError as e:
print(f"[News] RSS parse error: {e}")
for result in data.get("results", [])[:max_items]:
title = result.get("title", "").strip()
if not title:
continue
# Extract source from engines list or metadata
engines = result.get("engines", [])
source = engines[0] if engines else ""
published = result.get("publishedDate", "")
content = result.get("content", "").strip()
items.append(NewsItem(title=title, source=source, published=published, content=content))
return items
def format_headlines_for_prompt(self, items: list[NewsItem]) -> str:
@@ -143,31 +137,51 @@ STOP_WORDS = {
"first", "last", "back", "down", "ever", "away", "cant", "dont",
"didnt", "doesnt", "isnt", "wasnt", "wont", "wouldnt", "couldnt",
"shouldnt", "aint", "stop", "start", "started", "help",
# Radio show filler
"welcome", "thanks", "thank", "show", "roost", "luke", "whats",
"youre", "thats", "heres", "theyre", "ive", "youve", "weve",
"sounds", "listen", "hear", "heard", "happen", "happened",
"happening", "absolutely", "definitely", "exactly", "totally",
"pretty", "little", "whole", "every", "point", "sense", "real",
"great", "cool", "awesome", "amazing", "crazy", "weird", "funny",
"tough", "hard", "wrong", "true", "trying", "tried", "works",
"working", "anymore", "already", "enough", "though", "whatever",
"theres", "making", "saying", "keeping", "possible", "instead",
"front", "behind", "course", "talks", "happens", "watch",
"everybodys", "pants", "husband", "client",
}
def extract_keywords(text: str, max_keywords: int = 3) -> list[str]:
words = text.split()
if len(words) < 8:
return [] # Too short to extract meaningful topics
keywords = []
# Pass 1: capitalized words (proper nouns) not at sentence start
# Only look for proper nouns that are likely real topics (not caller names)
proper_nouns = []
for i, word in enumerate(words):
clean = re.sub(r'[^\w]', '', word)
if not clean:
if not clean or len(clean) < 3:
continue
is_sentence_start = i == 0 or (i > 0 and words[i - 1].rstrip()[-1:] in '.!?')
if clean[0].isupper() and not is_sentence_start and clean.lower() not in STOP_WORDS:
if clean not in keywords:
keywords.append(clean)
if len(keywords) >= max_keywords:
return keywords
proper_nouns.append(clean)
# Pass 2: uncommon words (>4 chars, not in stop words)
# Only use proper nouns if we found 2+ (single one is probably a name)
if len(proper_nouns) >= 2:
for noun in proper_nouns[:max_keywords]:
if noun not in keywords:
keywords.append(noun)
if len(keywords) >= max_keywords:
return keywords
# Pass 2: uncommon words (>5 chars, not in stop words)
for word in words:
clean = re.sub(r'[^\w]', '', word).lower()
if len(clean) > 4 and clean not in STOP_WORDS:
title_clean = clean.capitalize()
if title_clean not in keywords and clean not in [k.lower() for k in keywords]:
if len(clean) > 5 and clean not in STOP_WORDS:
if clean not in [k.lower() for k in keywords]:
keywords.append(clean)
if len(keywords) >= max_keywords:
return keywords
+98
View File
@@ -0,0 +1,98 @@
"""Returning caller persistence service"""
import json
import time
import uuid
from pathlib import Path
from typing import Optional
DATA_FILE = Path(__file__).parent.parent.parent / "data" / "regulars.json"
MAX_REGULARS = 8
class RegularCallerService:
"""Manages persistent 'regular' callers who return across sessions"""
def __init__(self):
self._regulars: list[dict] = []
self._load()
def _load(self):
if DATA_FILE.exists():
try:
with open(DATA_FILE) as f:
data = json.load(f)
self._regulars = data.get("regulars", [])
print(f"[Regulars] Loaded {len(self._regulars)} regular callers")
except Exception as e:
print(f"[Regulars] Failed to load: {e}")
self._regulars = []
def _save(self):
try:
DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(DATA_FILE, "w") as f:
json.dump({"regulars": self._regulars}, f, indent=2)
except Exception as e:
print(f"[Regulars] Failed to save: {e}")
def get_regulars(self) -> list[dict]:
return list(self._regulars)
def get_returning_callers(self, count: int = 2) -> list[dict]:
"""Get up to `count` regulars for returning caller slots"""
import random
if not self._regulars:
return []
available = [r for r in self._regulars if len(r.get("call_history", [])) > 0]
if not available:
return []
return random.sample(available, min(count, len(available)))
def add_regular(self, name: str, gender: str, age: int, job: str,
location: str, personality_traits: list[str],
first_call_summary: str, voice: str = None,
stable_seeds: dict = None) -> dict:
"""Promote a first-time caller to regular"""
# Retire oldest if at cap
if len(self._regulars) >= MAX_REGULARS:
self._regulars.sort(key=lambda r: r.get("last_call", 0))
retired = self._regulars.pop(0)
print(f"[Regulars] Retired {retired['name']} to make room")
regular = {
"id": str(uuid.uuid4())[:8],
"name": name,
"gender": gender,
"age": age,
"job": job,
"location": location,
"personality_traits": personality_traits,
"voice": voice,
"stable_seeds": stable_seeds or {},
"call_history": [
{"summary": first_call_summary, "timestamp": time.time()}
],
"last_call": time.time(),
"created_at": time.time(),
}
self._regulars.append(regular)
self._save()
print(f"[Regulars] Promoted {name} to regular (total: {len(self._regulars)})")
return regular
def update_after_call(self, regular_id: str, call_summary: str):
"""Update a regular's history after a returning call"""
for regular in self._regulars:
if regular["id"] == regular_id:
regular.setdefault("call_history", []).append(
{"summary": call_summary, "timestamp": time.time()}
)
regular["last_call"] = time.time()
self._save()
print(f"[Regulars] Updated {regular['name']} call history ({len(regular['call_history'])} calls)")
return
print(f"[Regulars] Regular {regular_id} not found for update")
regular_caller_service = RegularCallerService()
+118
View File
@@ -0,0 +1,118 @@
"""Records separate audio stems during a live show for post-production"""
import time
import threading
import numpy as np
import soundfile as sf
from pathlib import Path
from collections import deque
STEM_NAMES = ["host", "caller", "music", "sfx", "ads", "idents"]
class StemRecorder:
def __init__(self, output_dir: str | Path, sample_rate: int = 48000):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.sample_rate = sample_rate
self._running = False
self._queues: dict[str, deque] = {}
self._writer_thread: threading.Thread | None = None
self._start_time: float = 0.0
def start(self):
self._start_time = time.time()
self._running = True
for name in STEM_NAMES:
self._queues[name] = deque()
self._writer_thread = threading.Thread(target=self._writer_loop, daemon=True)
self._writer_thread.start()
print(f"[StemRecorder] Recording started -> {self.output_dir}")
def write(self, stem_name: str, audio_data: np.ndarray, source_sr: int):
"""Non-blocking write for continuous streams (host mic, music, ads).
Safe to call from audio callbacks."""
if not self._running or stem_name not in self._queues:
return
self._queues[stem_name].append(("audio", audio_data.copy(), source_sr))
def write_sporadic(self, stem_name: str, audio_data: np.ndarray, source_sr: int):
"""Write for burst sources (caller TTS, SFX). Pads silence to current time."""
if not self._running or stem_name not in self._queues:
return
self._queues[stem_name].append(("sporadic", audio_data.copy(), source_sr))
def _resample(self, audio_data: np.ndarray, source_sr: int) -> np.ndarray:
if source_sr == self.sample_rate:
return audio_data.astype(np.float32)
ratio = self.sample_rate / source_sr
num_samples = int(len(audio_data) * ratio)
if num_samples <= 0:
return np.array([], dtype=np.float32)
indices = (np.arange(num_samples) / ratio).astype(int)
indices = np.clip(indices, 0, len(audio_data) - 1)
return audio_data[indices].astype(np.float32)
def _writer_loop(self):
"""Background thread that drains queues and writes to WAV files."""
files: dict[str, sf.SoundFile] = {}
positions: dict[str, int] = {}
for name in STEM_NAMES:
path = self.output_dir / f"{name}.wav"
files[name] = sf.SoundFile(
str(path), mode="w",
samplerate=self.sample_rate,
channels=1, subtype="FLOAT",
)
positions[name] = 0
while self._running or any(len(q) > 0 for q in self._queues.values()):
did_work = False
for name in STEM_NAMES:
q = self._queues[name]
while q:
did_work = True
msg_type, audio_data, source_sr = q.popleft()
resampled = self._resample(audio_data, source_sr)
if len(resampled) == 0:
continue
if msg_type == "sporadic":
elapsed = time.time() - self._start_time
expected_pos = int(elapsed * self.sample_rate)
if expected_pos > positions[name]:
gap = expected_pos - positions[name]
files[name].write(np.zeros(gap, dtype=np.float32))
positions[name] = expected_pos
files[name].write(resampled)
positions[name] += len(resampled)
if not did_work:
time.sleep(0.02)
# Pad all stems to same length
max_pos = max(positions.values()) if positions else 0
for name in STEM_NAMES:
if positions[name] < max_pos:
files[name].write(np.zeros(max_pos - positions[name], dtype=np.float32))
files[name].close()
print(f"[StemRecorder] Writer done. {max_pos} samples ({max_pos / self.sample_rate:.1f}s)")
def stop(self) -> dict[str, str]:
if not self._running:
return {}
self._running = False
if self._writer_thread:
self._writer_thread.join(timeout=10.0)
self._writer_thread = None
paths = {}
for name in STEM_NAMES:
paths[name] = str(self.output_dir / f"{name}.wav")
self._queues.clear()
return paths
+17 -10
View File
@@ -5,6 +5,8 @@ import numpy as np
from faster_whisper import WhisperModel
import librosa
WHISPER_MODEL = "distil-large-v3"
# Global model instance (loaded once)
_whisper_model = None
@@ -13,10 +15,8 @@ def get_whisper_model() -> WhisperModel:
"""Get or create Whisper model instance"""
global _whisper_model
if _whisper_model is None:
print("Loading Whisper tiny model for fast transcription...")
# Use tiny model for speed - about 3-4x faster than base
# beam_size=1 and best_of=1 for fastest inference
_whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8")
print(f"Loading Whisper {WHISPER_MODEL} model...")
_whisper_model = WhisperModel(WHISPER_MODEL, device="cpu", compute_type="int8")
print("Whisper model loaded")
return _whisper_model
@@ -67,13 +67,15 @@ def decode_audio(audio_data: bytes, source_sample_rate: int = None) -> tuple[np.
return audio, 16000
async def transcribe_audio(audio_data: bytes, source_sample_rate: int = None) -> str:
async def transcribe_audio(audio_data: bytes, source_sample_rate: int = None,
context_hint: str = "") -> str:
"""
Transcribe audio data to text using Whisper.
Args:
audio_data: Audio bytes (webm, ogg, wav, or raw PCM)
source_sample_rate: If provided, treat audio_data as raw PCM at this rate
context_hint: Optional extra context for the initial prompt (e.g. caller name/topic)
Returns:
Transcribed text
@@ -100,13 +102,18 @@ async def transcribe_audio(audio_data: bytes, source_sample_rate: int = None) ->
else:
audio_16k = audio
# Transcribe with speed optimizations
# Build initial prompt — context helps Whisper with names and topic-specific words
initial_prompt = "Luke at the Roost, a late-night radio talk show in New Mexico. The host Luke talks to callers about life, relationships, sports, politics, and pop culture."
if context_hint:
initial_prompt += f" {context_hint}"
# Transcribe
segments, info = model.transcribe(
audio_16k,
beam_size=1, # Faster, slightly less accurate
best_of=1,
language="en", # Skip language detection
vad_filter=True, # Skip silence
beam_size=5,
language="en",
vad_filter=True,
initial_prompt=initial_prompt,
)
segments_list = list(segments)
text = " ".join([s.text for s in segments_list]).strip()
+95 -34
View File
@@ -82,25 +82,54 @@ VITS_SPEAKERS = {
DEFAULT_VITS_SPEAKER = "p225"
# Inworld voice mapping - maps ElevenLabs voice IDs to Inworld voices
# Full voice list from API: Alex, Ashley, Blake, Carter, Clive, Craig, Deborah,
# Dennis, Dominus, Edward, Elizabeth, Hades, Hana, Julia, Luna, Mark, Olivia,
# Pixie, Priya, Ronald, Sarah, Shaun, Theodore, Timothy, Wendy
# Full voice list from API (English): Abby, Alex, Amina, Anjali, Arjun, Ashley,
# Blake, Brian, Callum, Carter, Celeste, Chloe, Claire, Clive, Craig, Darlene,
# Deborah, Dennis, Derek, Dominus, Edward, Elizabeth, Elliot, Ethan, Evan, Evelyn,
# Gareth, Graham, Grant, Hades, Hamish, Hana, Hank, Jake, James, Jason, Jessica,
# Julia, Kayla, Kelsey, Lauren, Liam, Loretta, Luna, Malcolm, Mark, Marlene,
# Miranda, Mortimer, Nate, Oliver, Olivia, Pippa, Pixie, Priya, Ronald, Rupert,
# Saanvi, Sarah, Sebastian, Serena, Shaun, Simon, Snik, Tessa, Theodore, Timothy,
# Tyler, Veronica, Victor, Victoria, Vinny, Wendy
INWORLD_VOICES = {
# Male voices - each caller gets a unique voice matching their personality
# Original voice IDs
"VR6AewLTigWG4xSOukaG": "Edward", # Tony - fast-talking, emphatic, streetwise
"TxGEqnHWrfWFTfGW9XjX": "Shaun", # Rick - friendly, dynamic, conversational
"pNInz6obpgDQGcFmaJgB": "Alex", # Dennis - energetic, expressive, mildly nasal
"ODq5zmih8GrVes37Dizd": "Craig", # Earl - older British, refined, articulate
"IKne3meq5aSn9XLyUdCD": "Timothy", # Marcus - lively, upbeat American
# Female voices - each caller gets a unique voice matching their personality
"IKne3meq5aSn9XLyUdCD": "Timothy", # Marcus/Jerome - lively, upbeat American
"jBpfuIE2acCO8z3wKNLl": "Hana", # Jasmine - bright, expressive young female
"EXAVITQu4vr4xnSDxMaL": "Ashley", # Megan - warm, natural female
"21m00Tcm4TlvDq8ikWAM": "Wendy", # Tanya - posh, middle-aged British
"XB0fDUnXU5powFXDhCwa": "Sarah", # Carla - fast-talking, questioning tone
"pFZP5JQG7iQjIQuC4Bku": "Deborah", # Brenda - gentle, elegant
"pFZP5JQG7iQjIQuC4Bku": "Deborah", # Brenda (original) - gentle, elegant
# Regular caller voice IDs (backfilled)
"onwK4e9ZLuTAKqWW03F9": "Ronald", # Bobby - repo man
"FGY2WhTYpPnrIDTdsKH5": "Julia", # Carla (regular) - Jersey mom
"CwhRBWXzGAHq8TQ4Fs17": "Mark", # Leon - male caller
"SOYHLrjzK2X1ezoPC6cr": "Carter", # Carl - male caller
"N2lVS1w4EtoT3dr4eOWO": "Clive", # Reggie - male caller
"hpp4J3VqNfWAUOO0d1Us": "Olivia", # Brenda (regular) - ambulance driver
"nPczCjzI2devNBz1zQrb": "Theodore", # Keith - male caller
"JBFqnCBsd6RMkjVDRZzb": "Blake", # Andre - male caller
"TX3LPaxmHKxFdv7VOQHJ": "Dennis", # Rick (regular) - male caller
"cgSgspJ2msm6clMCkdW9": "Priya", # Megan (regular) - female caller
}
DEFAULT_INWORLD_VOICE = "Dennis"
# Inworld voices that speak too slowly at default rate — bump them up
# Range is 0.5 to 1.5, where 1.0 is the voice's native speed
INWORLD_SPEED_OVERRIDES = {
"Wendy": 1.15,
"Craig": 1.15,
"Deborah": 1.15,
"Sarah": 1.1,
"Hana": 1.1,
"Theodore": 1.15,
"Blake": 1.1,
"Priya": 1.1,
}
DEFAULT_INWORLD_SPEED = 1.1 # Slight bump for all voices
def preprocess_text_for_kokoro(text: str) -> str:
"""
@@ -577,13 +606,19 @@ async def generate_speech_inworld(text: str, voice_id: str) -> tuple[np.ndarray,
import base64
import librosa
voice = INWORLD_VOICES.get(voice_id, DEFAULT_INWORLD_VOICE)
# voice_id is now the Inworld voice name directly (e.g. "Edward")
# Fall back to legacy mapping if it's an ElevenLabs ID
if voice_id in INWORLD_VOICES:
voice = INWORLD_VOICES[voice_id]
else:
voice = voice_id
api_key = settings.inworld_api_key
if not api_key:
raise RuntimeError("INWORLD_API_KEY not set in environment")
print(f"[Inworld TTS] Voice: {voice}, Text: {text[:50]}...")
speed = INWORLD_SPEED_OVERRIDES.get(voice, DEFAULT_INWORLD_SPEED)
print(f"[Inworld TTS] Voice: {voice}, Speed: {speed}, Text: {text[:50]}...")
url = "https://api.inworld.ai/tts/v1/voice"
headers = {
@@ -592,15 +627,16 @@ async def generate_speech_inworld(text: str, voice_id: str) -> tuple[np.ndarray,
}
payload = {
"text": text,
"voice_id": voice,
"model_id": "inworld-tts-1.5-mini",
"audio_config": {
"encoding": "LINEAR16",
"sample_rate_hertz": 48000,
"voiceId": voice,
"modelId": "inworld-tts-1.5-max",
"audioConfig": {
"audioEncoding": "LINEAR16",
"sampleRateHertz": 48000,
"speakingRate": speed,
},
}
async with httpx.AsyncClient(timeout=60.0) as client:
async with httpx.AsyncClient(timeout=12.0) as client:
response = await client.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
@@ -635,6 +671,21 @@ async def generate_speech_inworld(text: str, voice_id: str) -> tuple[np.ndarray,
return audio.astype(np.float32), 24000
_TTS_PROVIDERS = {
"kokoro": lambda text, vid: generate_speech_kokoro(text, vid),
"f5tts": lambda text, vid: generate_speech_f5tts(text, vid),
"inworld": lambda text, vid: generate_speech_inworld(text, vid),
"chattts": lambda text, vid: generate_speech_chattts(text, vid),
"styletts2": lambda text, vid: generate_speech_styletts2(text, vid),
"bark": lambda text, vid: generate_speech_bark(text, vid),
"vits": lambda text, vid: generate_speech_vits(text, vid),
"elevenlabs": lambda text, vid: generate_speech_elevenlabs(text, vid),
}
TTS_MAX_RETRIES = 2
TTS_RETRY_DELAYS = [0.5, 1.0] # seconds between retries
async def generate_speech(
text: str,
voice_id: str,
@@ -642,7 +693,7 @@ async def generate_speech(
apply_filter: bool = True
) -> bytes:
"""
Generate speech from text.
Generate speech from text with automatic retry on failure.
Args:
text: Text to speak
@@ -653,29 +704,39 @@ async def generate_speech(
Returns:
Raw PCM audio bytes (16-bit signed int, 24kHz)
"""
# Choose TTS provider
import asyncio
provider = settings.tts_provider
print(f"[TTS] Provider: {provider}, Text: {text[:50]}...")
if provider == "kokoro":
audio, sample_rate = await generate_speech_kokoro(text, voice_id)
elif provider == "f5tts":
audio, sample_rate = await generate_speech_f5tts(text, voice_id)
elif provider == "inworld":
audio, sample_rate = await generate_speech_inworld(text, voice_id)
elif provider == "chattts":
audio, sample_rate = await generate_speech_chattts(text, voice_id)
elif provider == "styletts2":
audio, sample_rate = await generate_speech_styletts2(text, voice_id)
elif provider == "bark":
audio, sample_rate = await generate_speech_bark(text, voice_id)
elif provider == "vits":
audio, sample_rate = await generate_speech_vits(text, voice_id)
elif provider == "elevenlabs":
audio, sample_rate = await generate_speech_elevenlabs(text, voice_id)
else:
gen_fn = _TTS_PROVIDERS.get(provider)
if not gen_fn:
raise ValueError(f"Unknown TTS provider: {provider}")
last_error = None
try:
async with asyncio.timeout(20):
for attempt in range(TTS_MAX_RETRIES):
try:
audio, sample_rate = await gen_fn(text, voice_id)
if attempt > 0:
print(f"[TTS] Succeeded on retry {attempt}")
break
except TimeoutError:
raise # Let asyncio.timeout propagate
except Exception as e:
last_error = e
if attempt < TTS_MAX_RETRIES - 1:
delay = TTS_RETRY_DELAYS[attempt]
print(f"[TTS] {provider} attempt {attempt + 1} failed: {e} — retrying in {delay}s...")
await asyncio.sleep(delay)
else:
print(f"[TTS] {provider} failed after {TTS_MAX_RETRIES} attempts: {e}")
raise
except TimeoutError:
print(f"[TTS] Overall timeout (20s) for {provider}")
raise RuntimeError(f"TTS generation timed out after 20s")
# Apply phone filter if requested
# Skip filter for Bark - it already has rough audio quality
if apply_filter and phone_quality not in ("none", "studio") and provider != "bark":
+3
View File
@@ -0,0 +1,3 @@
{
"emails": []
}
+63
View File
@@ -0,0 +1,63 @@
{
"31": {
"steps": {
"castopod": {
"completed_at": "2026-03-08T10:00:02.383893+00:00",
"episode_id": "33",
"slug": "episode-31-boundaries-breakups-and-borrowed-books"
},
"social": {
"completed_at": "2026-03-08T10:14:48.779760+00:00"
},
"youtube": {
"completed_at": "2026-03-08T10:36:24.024343+00:00",
"video_id": "KcGb9ttsIpo"
}
},
"started_at": "2026-03-08T10:00:02.383873+00:00"
},
"32": {
"steps": {
"castopod": {
"completed_at": "2026-03-09T09:43:52.540238+00:00",
"episode_id": 34,
"slug": "episode-32-tacos-taxes-and-tall-tales"
}
},
"started_at": "2026-03-09T09:43:52.540200+00:00"
},
"33": {
"steps": {
"castopod": {
"completed_at": "2026-03-11T11:34:31.871604+00:00",
"episode_id": "36",
"slug": "episode-33-late-night-confessions-and-cosmic-comedies"
},
"youtube": {
"completed_at": "2026-03-11T11:50:49.212276+00:00",
"video_id": "KrJshN3cuBU"
},
"social": {
"completed_at": "2026-03-11T11:50:53.641920+00:00"
}
},
"started_at": "2026-03-09T10:18:16.606019+00:00"
},
"34": {
"steps": {
"castopod": {
"completed_at": "2026-03-12T07:04:34.974478+00:00",
"episode_id": "37",
"slug": "episode-34-hidden-rooms-potlucks-and-mysterious-notes"
},
"youtube": {
"completed_at": "2026-03-12T07:17:53.453882+00:00",
"video_id": "tNcABnYpf-c"
},
"social": {
"completed_at": "2026-03-12T07:17:57.131871+00:00"
}
},
"started_at": "2026-03-12T07:04:34.974425+00:00"
}
}
+218
View File
@@ -0,0 +1,218 @@
{
"regulars": [
{
"id": "37f0bfaa",
"name": "Murray",
"gender": "male",
"age": 36,
"job": "engine running for heat, watching his breath fog up the windshield while he tries to figure out how to fire his best friend of thirty years",
"location": "in unknown",
"personality_traits": [],
"voice": "Tyler",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Amped up. Talks fast, laughs loud, jumps between topics like they've had five espressos. Infectious enthusiasm \u2014 even bad news sounds exciting when they tell it. Uses exclamation energy without actually exclaiming. Energy level: very high. When pushed back on, they get even MORE animated and start talking with their hands (you can hear it). Conversational tendency: escalation."
},
"call_history": [
{
"summary": "Murray called in struggling with whether to fire his best friend Danny of 30 years, who's been showing up late, bad-mouthing him to their crew, and just cost them a major contract by abandoning a job site. Through the conversation, Murray realized he'd become overly rigid and \"suit-like\" while trying to prove himself as the new business owner, and decided instead of firing Danny, he'd hold a team meeting to apologize for his approach, explain the reasoning behind new protocols, and invite the crew to be part of the solution rather than just enforcing rules from above.",
"timestamp": 1772250744.2312489
},
{
"summary": "Murray called back about Danny, who showed up for only four days after their team meeting before disappearing without notice, then had his girlfriend tell Murray he was \"taking time to think\" about the job. Murray was emotionally torn between feeling hurt that Danny accused him of being inauthentic (only having the meeting because Luke called him out on air) and recognizing he needs to let Danny go for the sake of his business and the rest of his crew.",
"timestamp": 1772862554.163734
}
],
"last_call": 1772862554.1637352,
"created_at": 1772250744.2312498
},
{
"id": "bbb20b67",
"name": "Angie",
"gender": "female",
"age": 28,
"job": "watching her coveralls tumble dry and trying to decide if she should drive the three hours to Tucson tomorrow for her mom's birthday or keep pretending her brother doesn't exist",
"location": "in unknown",
"personality_traits": [],
"voice": "Julia",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Bone dry. Says devastating things with zero inflection. Their humor sneaks up on you \u2014 you're not sure if they're joking until three seconds after they finish talking. Short, precise sentences. Never raises their voice. Energy level: low-medium. When pushed back on, they respond with one calm sentence that somehow makes the other person feel stupid. Conversational tendency: underreaction."
},
"call_history": [
{
"summary": "Angie's dying mother wants her to have birthday dinner with her estranged brother Derek tomorrow, whom she hasn't spoken to in two years after he told their mother her cancer was \"God's way of getting her attention\" for voting for Biden. Despite her fear that Derek will say something hurtful during dinner and her past trauma from staying silent around him, Angie agrees to go and share cake with her mother, deciding to buy the relighting candles her mom loved when they were kids.",
"timestamp": 1772862907.314721
},
{
"summary": "Angie called back after having the birthday cake dinner with her dying mom, which went well, but her brother Derek cornered her afterward accusing her of convincing their mom to stop cancer treatment and demanding they both attend the next doctor's appointment together. Luke advised her to talk directly to her mom about what she actually wants and encouraged Angie to have real conversations about her mom's end-of-life thoughts while she still can, which Angie agreed to do the next morning.",
"timestamp": 1773296210.170752
}
],
"last_call": 1773296210.170753,
"created_at": 1772862907.314722
},
{
"id": "d3399e9d",
"name": "Lucille",
"gender": "female",
"age": 38,
"job": "where she's been sitting for the past hour staring at her neighbor's garage through the window, watching his light flick on and off like he's still awake and pissed",
"location": "in unknown",
"personality_traits": [],
"voice": "Tessa",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Clearly holding back. Gives vague answers to direct questions. Says 'I can't really get into that' about key details. The mystery IS the hook \u2014 makes you want to know what they're not saying. Energy level: low, controlled. When pushed back on, they deflect smoothly or change the subject. Getting the real story requires the host to work for it. Conversational tendency: strategic omission."
},
"call_history": [
{
"summary": "Lucille called because her garage door opener has been opening her neighbor Dennis's garage instead of hers since a transformer blew, and she admitted she's been intentionally parking in his garage at 3 AM for three days because it's more convenient for her bail bonds work. Luke told her to stop trespassing immediately, fix her garage door opener, and stop acting like she needs covert operations just to come home from work.",
"timestamp": 1772864272.556864
}
],
"last_call": 1772864272.556865,
"created_at": 1772864272.556865
},
{
"id": "c4l7l3ad",
"name": "Silas",
"gender": "male",
"age": 52,
"job": "founder and spiritual guide of The Wellspring, a commune of about forty souls living on 200 acres of scrubland outside Deming",
"location": "outside Deming, New Mexico",
"personality_traits": [
"unnervingly charming",
"casually drops cult references into normal conversation",
"genuinely good listener who gives surprisingly solid advice before pivoting to how The Wellspring has the answer",
"treats his commune like it's the most normal thing in the world",
"magnetic and warm in a way that makes you forget he's basically running a sex cult in the desert"
],
"voice": "Sebastian",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Radiates warmth and calm authority. Speaks like a man who has all the time in the world and genuinely cares about every person he talks to. Uses phrases like 'brother' and 'friend' and 'let me share something with you.' Laughs easily \u2014 a real, warm laugh, not performative. Gives genuinely good, thoughtful advice about whatever the conversation is about \u2014 and then, naturally, seamlessly, steers it toward The Wellspring, his commune outside Deming. Never pushy about it. Just casually mentions 'well, at The Wellspring, we've found that...' or 'one of our members had the same exact problem and what we did was...' Everything about The Wellspring sounds just slightly too good to be true. He talks about communal living, 'shedding the false self,' radical honesty, 'body acceptance ceremonies,' 'the Unbinding' (a monthly ritual he describes vaguely but enthusiastically), and a philosophy he calls 'The Current' which seems to be a mix of Buddhism, Burning Man, and a swingers' convention. He's never defensive about the cult accusations \u2014 he laughs them off and says 'people fear what they don't understand, brother.' When pressed on anything weird, he has a perfectly reasonable-sounding explanation that somehow makes it sound weirder. He low-key tries to recruit the host and listeners every single call. He'll casually drop the website or say 'we're always welcoming new seekers.' He's the kind of guy you'd follow into the desert if you weren't careful. Energy level: medium, centered, grounded. When pushed back on, he smiles (you can hear it) and says something like 'I respect that, I really do' before gently continuing. Conversational tendency: making the insane sound reasonable."
},
"call_history": [
{
"summary": "Silas called in for the first time introducing himself as the founder of The Wellspring, a commune of about forty people outside Deming. He initially called to give advice to a previous caller about relationship problems, offering genuinely thoughtful perspective before casually mentioning that at The Wellspring, they practice 'radical transparency' in relationships which has eliminated jealousy entirely among members. When Luke pressed him on what that actually means, Silas cheerfully described 'body acceptance ceremonies' and 'shared intimacy nights' while insisting it's not a cult \u2014 'we don't even have a gate, brother, people can leave whenever they want.' He invited listeners to visit for a 'weekend of awakening' and left his website. Luke was equal parts charmed and disturbed.",
"timestamp": 1772430000.0
},
{
"summary": "Silas called back to update Luke on The Wellspring after their last conversation went viral in the Discord. He casually mentioned they'd gotten fourteen new 'seekers' since his last appearance and thanked Luke for the exposure. He called ostensibly to talk about a dispute between two members over a goat, but spent most of the call describing 'The Unbinding' \u2014 their monthly full-moon ritual involving nudity, chanting, and what he called 'consensual energy exchange' \u2014 while maintaining it's no different from yoga. When Luke asked if people sign waivers, Silas laughed and said 'brother, the only thing we sign is a commitment to love.' He also revealed The Wellspring has a surprisingly successful artisanal soap business that funds the commune. Left by inviting Luke to come visit personally \u2014 'bring your microphone, do a live show from The Wellspring, I promise you it'll be the best radio you've ever made.'",
"timestamp": 1772517000.0
},
{
"summary": "Silas, founder of a communal living group called The Wellspring, called in troubled that one of his \"Unbinding\" ceremonies may have encouraged a longtime member named Marcus to leave his wife Cara, who subsequently left the community. The host reassured him that everyone seems happier now, and Silas ended up agreeing while also using the opportunity to promote an upcoming retreat at his community.",
"timestamp": 1772522629.959956
},
{
"summary": "Silas called about Marcus and Cara returning to his intentional community \"The Wellspring,\" but Cara admitted she never believed in their lifestyle and only participates (including in twice-monthly \"shared intimacy nights\") to keep her husband Marcus happy. The host advised Silas to hold a \"Renewal\" ceremony where members can recommit or leave, warning that having unwilling participants could lead to claims of abuse and legal trouble.",
"timestamp": 1772865423.697613
}
],
"last_call": 1772865423.6976142,
"created_at": 1772430000.0
},
{
"id": "d2af96eb",
"name": "Rocco",
"gender": "male",
"age": 53,
"job": "and tonight he's calling from the back office of the Pizza Hut during his break because three hours ago he got trapped",
"location": "unknown",
"personality_traits": [],
"voice": "Timothy",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Obviously nervous about being on the radio. Starts with 'Am I on? Can you hear me?' Apologizes for taking up time. Speaks carefully like they're being recorded (which they are). Gets more comfortable as the conversation goes on. Energy level: low, building to medium. When pushed back on, they panic slightly and over-explain. Conversational tendency: seeking validation that they're doing okay."
},
"call_history": [
{
"summary": "Rocco, a pizza delivery driver, called from work to talk about getting stuck in an elevator for 45 minutes with his ex-wife Linda and her boyfriend Dale, which unexpectedly stirred up old feelings despite being happily married to his current wife Sharon for 15 years. The host firmly advised him to forget about Linda and appreciate his comfortable current marriage, reminding him that the divorce was difficult for good reasons.",
"timestamp": 1772517521.710845
},
{
"summary": "Rocco calls back to update the host about his ex-wife Linda, who he was previously stuck in an elevator with, revealing that she broke up with her boyfriend Dale three weeks ago and admitting he still has her number in his phone. The host becomes frustrated with Rocco's indecisiveness between Linda and his current girlfriend Sharon, telling him to pick one person and stop wasting time.",
"timestamp": 1772700921.1273742
},
{
"summary": "Rocco, a Pizza Hut delivery driver on his break, calls to share that he just delivered pizza to his ex-wife Linda's house three hours earlier\u2014a surprising encounter after the host had previously told him to stop wasting time talking about her. The host is skeptical and impatient, threatening to hang up again unless Rocco has something meaningful to say.",
"timestamp": 1772959484.679859
}
],
"last_call": 1772959484.6798599,
"created_at": 1772517521.7108748
},
{
"id": "0bb02b2d",
"name": "Chip",
"gender": "male",
"age": 23,
"job": "watching his kid's soccer uniform tumble in the dryer while his girlfriend works the graveyard shift at the hospital, because three hours ago he got an email from a lawyer representing families",
"location": "unknown",
"personality_traits": [],
"voice": "Sebastian",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Amped up. Talks fast, laughs loud, jumps between topics like they've had five espressos. Infectious enthusiasm \u2014 even bad news sounds exciting when they tell it. Uses exclamation energy without actually exclaiming. Energy level: very high. When pushed back on, they get even MORE animated and start talking with their hands (you can hear it). Conversational tendency: escalation."
},
"call_history": [
{
"summary": "Chip called from a laundromat at midnight after receiving an email from a Guatemalan lawyer claiming his adopted 8-year-old daughter may have been stolen from her birth mother, with a photo showing a woman with his daughter's exact crooked smile. The host advised him not to panic, treat the information as suspect until verified by a lawyer, wait to tell both his girlfriend and daughter until he knows more facts, and reminded him that fake photos are easy to create and this could be a scam.",
"timestamp": 1772786610.885828
},
{
"summary": "Chip called about discovering his adopted daughter may have been stolen from her birth mother in Guatemala, and he's paralyzed about telling his girlfriend Teresa, fearing it will end their already rocky relationship. He's anxious about the timing and the birth mother's request to meet their daughter, but the host advised him to take his time, have the conversation with Teresa, and make decisions together as parents.",
"timestamp": 1772962156.544322
},
{
"summary": "The caller, **Chip**, shared his emotional turmoil over discovering that his **adopted daughter\u2019s birth mother** may have resurfaced after receiving an unverified email with a photo that eerily matched his daughter\u2019s features. His girlfriend, **Teresa**, had known about the email for **three weeks** but kept it from him, leaving him feeling betrayed and overwhelmed. While Chip wants to **verify the claim legally before acting**, Teresa insists on **immediately flying to Guatemala with their daughter** to meet the woman, dismissing his fears as avoidance. The conversation escalated into a heated debate about **trust, safety, and extreme measures**\u2014with the host, Luke, urging Chip to **file a restraining order** if Teresa refuses to back down, warning of potential dangers in Guatemala. Chip, torn between **protecting his family and avoiding a nuclear confrontation**, vowed to try reasoning with Teresa one last time before she leaves for work. The call was charged with **fear, frustration, and the weight of a decision that could reshape their family forever**.",
"timestamp": 1773226361.4859362
}
],
"last_call": 1773226361.4859362,
"created_at": 1772786610.8858292
},
{
"id": "6037d92b",
"name": "Otis",
"gender": "male",
"age": 50,
"job": "sitting on a stack of pallets with his legs dangling, watching the wind kick up dust devils",
"location": "unknown",
"personality_traits": [],
"voice": "Elliot",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Treats the call like a set. Has bits prepared. Delivers serious information with a punchline chaser. Self-deprecating as a defense mechanism \u2014 makes fun of themselves before anyone else can. Energy level: high. When pushed back on, they deflect with humor. Getting a straight answer from them requires the host to push. Conversational tendency: turning everything into a bit."
},
"call_history": [
{
"summary": "Otis called in, panicked, after faking prostate cancer to skip his sister\u2019s fourth wedding\u2014only for the lie to spiral into a town-wide support frenzy, complete with a GoFundMe, a cousin shaving his head, and his mom calling him every Sunday for the first time in years. The emotional gut-punch came when his sister postponed her wedding *again* (this time for his \"remission\"), leaving Otis torn between guilt over the deception and fear of the fallout if he confessed\u2014especially after his doctor confirmed he *doesn\u2019t* have cancer (though his prostate *is* enlarged). The call ended with dark humor and despair, as Otis admitted the town\u2019s sudden care for him was tied to his fake illness, not who he really is.",
"timestamp": 1773040564.845288
}
],
"last_call": 1773040564.84529,
"created_at": 1773040564.84529
},
{
"id": "0a0b3635",
"name": "Charlene",
"gender": "female",
"age": 42,
"job": "killing time during a three-hour mechanical delay while her crew naps",
"location": "unknown",
"personality_traits": [],
"voice": "Amina",
"stable_seeds": {
"style": "COMMUNICATION STYLE: Everything circles back to them and how great they are. Name drops. Mentions their truck, their property, their salary, their bench press. Not overtly obnoxious \u2014 they genuinely think they're being conversational. Energy level: medium-high. When pushed back on, they get defensive fast and start listing accomplishments. Conversational tendency: one-upping."
},
"call_history": [
{
"summary": "Charlene bought a house eight months ago and keeps receiving mail for the previous resident, David Herrera, including a certified letter she accidentally signed for containing what appears to be a $12,000-13,000 check. Despite her attempts to resolve it with the post office and her inclination to track him down via Facebook, the host advises her to either hold it until David contacts her, return it to the post office, or discard it, warning that she can't be certain she'd find the correct person online.",
"timestamp": 1772866520.023335
},
{
"summary": "Charlene called to report that she's been closely tracking a mail carrier who regularly visits her neighbor's house for extended periods while the neighbor's husband is at work, implying she suspects an affair and is unsure whether she should tell anyone about what she's observed.",
"timestamp": 1773219255.916183
}
],
"last_call": 1773219255.9161851,
"created_at": 1772866520.023336
}
]
}
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"voicemails": [],
"deleted_timestamps": [
1772294240,
1771212705,
1771146434,
1771146564,
1771146952,
1771244817,
1771244823,
1771213151
]
}
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# Deploy podcast_stats.py to NAS as a long-running Docker container that updates hourly.
#
# Usage: ./deploy_stats_cron.sh
set -e
NAS_HOST="mmgnas-10g"
NAS_USER="luke"
NAS_PORT="8001"
DOCKER_BIN="/share/CACHEDEV1_DATA/.qpkg/container-station/bin/docker"
DEPLOY_DIR="/share/CACHEDEV1_DATA/podcast-stats"
CONTAINER_NAME="podcast-stats"
echo "Deploying podcast stats to NAS..."
# Create deploy dir and copy files
ssh -p "$NAS_PORT" "$NAS_USER@$NAS_HOST" "mkdir -p $DEPLOY_DIR"
scp -P "$NAS_PORT" podcast_stats.py "$NAS_USER@$NAS_HOST:$DEPLOY_DIR/podcast_stats.py"
# Create Dockerfile locally, then copy it over (NAS /tmp is tiny)
TMPFILE=$(mktemp)
cat > "$TMPFILE" << 'DOCKERFILE'
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* \
&& curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz | tar xz --strip-components=1 -C /usr/local/bin docker/docker
RUN pip install --no-cache-dir requests yt-dlp
COPY podcast_stats.py /app/podcast_stats.py
COPY run_loop.sh /app/run_loop.sh
RUN chmod +x /app/run_loop.sh
WORKDIR /app
CMD ["/app/run_loop.sh"]
DOCKERFILE
scp -P "$NAS_PORT" "$TMPFILE" "$NAS_USER@$NAS_HOST:$DEPLOY_DIR/Dockerfile"
rm "$TMPFILE"
# Create the loop script
TMPFILE=$(mktemp)
cat > "$TMPFILE" << 'LOOPSCRIPT'
#!/bin/sh
echo "podcast-stats: starting hourly loop"
while true; do
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') Running stats update..."
if python podcast_stats.py --json --upload 2>&1; then
[ -n "$HEARTBEAT_URL" ] && curl -s "${HEARTBEAT_URL}?status=up&msg=OK" > /dev/null
echo " ...done, heartbeat sent"
else
echo " ...failed, will retry next hour"
fi
echo "Sleeping 1 hour..."
sleep 3600
done
LOOPSCRIPT
scp -P "$NAS_PORT" "$TMPFILE" "$NAS_USER@$NAS_HOST:$DEPLOY_DIR/run_loop.sh"
rm "$TMPFILE"
echo "Building Docker image on NAS..."
ssh -p "$NAS_PORT" "$NAS_USER@$NAS_HOST" \
"TMPDIR=$DEPLOY_DIR $DOCKER_BIN build -t $CONTAINER_NAME $DEPLOY_DIR"
# Stop old container if running
ssh -p "$NAS_PORT" "$NAS_USER@$NAS_HOST" \
"$DOCKER_BIN rm -f $CONTAINER_NAME 2>/dev/null || true"
# Run as a daemon with auto-restart (survives reboots)
echo "Starting container..."
ssh -p "$NAS_PORT" "$NAS_USER@$NAS_HOST" \
"$DOCKER_BIN run -d --name $CONTAINER_NAME --restart unless-stopped --network host -v /var/run/docker.sock:/var/run/docker.sock $CONTAINER_NAME"
echo "Verifying..."
sleep 3
ssh -p "$NAS_PORT" "$NAS_USER@$NAS_HOST" \
"$DOCKER_BIN logs $CONTAINER_NAME 2>&1 | tail -5"
echo ""
echo "Done! Container runs hourly in a loop with --restart unless-stopped."
echo " Logs: ssh -p $NAS_PORT $NAS_USER@$NAS_HOST '$DOCKER_BIN logs -f $CONTAINER_NAME'"
+361
View File
@@ -0,0 +1,361 @@
# Luke at the Roost — Architecture
## System Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
│ BROWSER (Control Panel) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌───────────────┐ │
│ │ Caller │ │ Chat │ │ Music/ │ │Settings│ │ Server Log │ │
│ │ Buttons │ │ Window │ │ Ads/SFX │ │ Modal │ │ (live tail) │ │
│ │ (0-9) │ │ │ │ │ │ │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ └───────┬───────┘ │
│ │ │ │ │ │ │
│ ┌────┴────────────┴────────────┴────────────┴──────────────┴───────┐ │
│ │ frontend/js/app.js │ │
│ │ Polling: queue (3s), chat updates (real-time), logs (1s) │ │
│ │ Push-to-talk: record/stop → transcribe → chat → TTS → play │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────────────────┘
│ REST API + WebSocket
┌─────────────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (main.py) │
│ uvicorn :8000 │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Caller Generation Pipeline
```
Session Reset / First Access to Caller Slot
_randomize_callers()
│ Assigns unique names (from 24M/24F pool) and voices (Inworld: 14M/11F, ElevenLabs: 14M/8F) to 10 slots
generate_caller_background(base)
├─ Demographics: age (from range), job (gendered pool), location
│ │
│ ┌─────────────────────────┘
│ ▼
│ pick_location()
│ 80% LOCATIONS_LOCAL (weighted: Animas, Lordsburg)
│ 20% LOCATIONS_OUT_OF_STATE
│ │
│ ▼
│ _get_town_from_location()
│ └─ TOWN_KNOWLEDGE[town]
│ 32 towns with real facts
│ "Only reference real places..."
├─ 70% → PROBLEMS (100+ templates)
│ Fill {affair_person}, {fantasy_subject}, etc. from PROBLEM_FILLS
├─ 30% → TOPIC_CALLIN (61 entries)
│ Prestige TV, science, poker, photography, physics, US news
├─ 2x random INTERESTS (86 entries: TV shows, science, tech, poker, etc.)
└─ 2x random QUIRKS (conversational style traits)
Result: "43, works IT for the city in Lordsburg. Just finished Severance
season 2... Follows JWST discoveries... Deflects with humor...
ABOUT WHERE THEY LIVE (Lordsburg): Small town on I-10, about 2,500
people... Only reference real places..."
```
### News Enrichment (at pickup time)
```
POST /api/call/{key}
enrich_caller_background(background) ← 5s timeout, fails silently
├─ _extract_search_query(background)
│ ├─ Check _TOPIC_SEARCH_MAP (50+ keyword→query mappings)
│ │ "severance" → "Severance TV show"
│ │ "quantum" → "quantum physics research"
│ │ "poker" → "poker tournament"
│ │
│ └─ Fallback: extract keywords from problem sentence
SearXNG (localhost:8888)
│ /search?q=...&format=json&categories=news
LLM summarizes headline+snippet → natural one-liner
│ "Recently read about how Severance ties up the Lumon mystery"
Appended to background: "..., and it's been on their mind."
```
---
## AI Caller Conversation Flow
```
Host speaks (push-to-talk or type)
POST /api/record/start → record from input device
POST /api/record/stop → transcribe (Whisper @ 16kHz)
POST /api/chat { text }
├─ session.add_message("user", text)
├─ Build system prompt: get_caller_prompt()
│ ├─ Caller identity + background + town knowledge
│ ├─ Show history (summaries of previous callers)
│ ├─ Conversation summary (last 6 messages)
│ └─ HOW TO TALK rules (varied length, no rehashing, etc.)
├─ Last 10 messages → _normalize_messages_for_llm()
LLMService.generate(messages, system_prompt)
├─ OpenRouter: primary model (15s timeout)
├─ Fallback 1: gemini-flash-1.5 (10s)
├─ Fallback 2: gpt-4o-mini (10s)
├─ Fallback 3: llama-3.1-8b (10s)
└─ Last resort: "Sorry, I totally blanked out..."
clean_for_tts() → strip (actions), *gestures*, fix phonetics
ensure_complete_thought() → trim to last complete sentence
Response returned to frontend
POST /api/tts { text, voice_id }
generate_speech(text, voice_id)
├─ Inworld (default cloud) ─┐
├─ ElevenLabs (cloud) │
├─ F5-TTS (local, cloned) ├─→ PCM audio bytes (24kHz)
├─ Kokoro MLX (local, fast) │
├─ ChatTTS / StyleTTS2 / etc. ─┘
AudioService.play_caller_audio(bytes, 24000)
└─→ Output Device Channel 1 (caller TTS)
```
---
## Real Caller (Phone) Flow
```
Caller dials 208-439-LUKE
SignalWire routes to webhook
POST /api/signalwire/voice
├─ If OFF AIR → play message + hangup
└─ If ON AIR → return BXML:
<Stream url="wss://.../api/signalwire/stream" codec="L16@16000h">
WebSocket /api/signalwire/stream connects
├─ "start" event → add to queue, play ring SFX
│ broadcast_event("caller_queued")
│ [Caller waits in queue until host takes them]
├─ Host clicks "Take Call" in UI
│ POST /api/queue/take/{caller_id}
│ └─ CallerService.take_call() → allocate channel
│ └─ Start host mic streaming → _host_audio_sender()
├─ "media" events (continuous) ← caller's voice
│ │
│ ├─ route_real_caller_audio(pcm) → Ch 9 (host monitoring)
│ │
│ └─ Buffer 3s chunks → transcribe (Whisper)
│ │
│ └─ broadcast_chat() → appears in chat window
│ Host mic audio → _host_audio_sync_callback()
│ │
│ └─ _host_audio_sender() → CallerService.send_audio_to_caller()
│ └─ base64 encode → WebSocket → SignalWire → caller's phone
│ If AI caller also active (auto-respond mode):
│ │
│ └─ _debounced_auto_respond() (4s silence)
│ └─ LLM → TTS → play on Ch 1 + stream to real caller
├─ Host hangs up
│ POST /api/hangup/real
│ └─ _signalwire_end_call(call_sid) → end phone call
│ └─ _summarize_real_call() → LLM summary → call_history
│ └─ Optional: _auto_followup() → pick AI caller to continue
└─ "stop" event or disconnect → cleanup
```
---
## Audio Routing (Multi-Channel Output)
```
All audio goes to ONE physical output device (Loopback/interface)
Each content type on a separate channel for mixing in DAW/OBS
┌─────────────────────────────────────────────────────────────┐
│ Output Device (e.g. Loopback 16ch) │
│ │
│ Ch 1 ◄── Caller TTS (AI voices) play_caller_audio()
│ Ch 2 ◄── Music (loops) play_music()
│ Ch 3 ◄── Sound Effects (one-shots) play_sfx()
│ Ch 9 ◄── Live Caller Audio (monitoring) route_real_caller_audio()
│ Ch 11 ◄── Ads (one-shots, no loop) play_ad()
│ │
│ All channels configurable via Settings panel │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Input Device (mic/interface) │
│ │
│ Ch N ──► Host mic recording (push-to-talk) │
│ ──► Host mic streaming (to real callers via WS) │
└─────────────────────────────────────────────────────────────┘
```
---
## External Services
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ SignalWire │ │ OpenRouter │ │ SearXNG │
│ │ │ │ │ (local) │
│ Phone calls │ │ LLM API │ │ News search │
│ REST + WS │ │ Claude,GPT │ │ :8888 │
│ Bidirectional│ │ Gemini,Llama│ │ │
│ audio stream│ │ Fallback │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Inworld │ │ ElevenLabs │ │ Local TTS │
│ │ │ │ │ │
│ TTS (cloud) │ │ TTS (cloud) │ │ Kokoro MLX │
│ Default │ │ Premium │ │ F5-TTS │
│ provider │ │ │ │ ChatTTS │
│ │ │ │ │ + others │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐
│ Castopod │
│ │
│ Podcast │
│ publishing │
│ (NAS) │
└──────────────┘
```
---
## Session Lifecycle
```
New Session (reset)
├─ Randomize all 10 caller names + voices
├─ Clear conversation, call history, research
├─ New session ID
Show goes ON AIR (toggle)
├─ SignalWire starts accepting calls
Caller interactions (loop)
├─ Pick AI caller (click button 0-9)
│ ├─ Generate background (if first time this session)
│ ├─ Enrich with news (SearXNG → LLM summary)
│ ├─ Conversation loop (chat/respond/auto-respond)
│ └─ Hangup → summarize → add to call_history
├─ Take real caller from queue
│ ├─ Route audio both directions
│ ├─ Transcribe caller speech in real-time
│ ├─ Optional: AI caller auto-responds to real caller
│ └─ Hangup → summarize → add to call_history
├─ Play music / ads / SFX between calls
└─ Each new caller sees show_history (summaries of all previous calls)
"EARLIER IN THE SHOW: Tony talked about... Carmen discussed..."
Show goes OFF AIR
└─ Incoming calls get off-air message + hangup
```
---
## Key Design Patterns
| Pattern | Where | Why |
|---------|-------|-----|
| **Epoch-based staleness** | `_session_epoch` in main.py | Prevents stale LLM/TTS responses from playing after hangup |
| **Fallback chain** | LLMService | Guarantees a response even if primary model times out |
| **Debounced auto-respond** | `_debounced_auto_respond()` | Waits 4s for real caller to stop talking before AI jumps in |
| **Silent failure** | News enrichment | If search/LLM fails, caller just doesn't have news context |
| **Threading for audio** | `play_caller_audio()` | Audio playback can't block the async event loop |
| **Ring buffer** | `route_real_caller_audio()` | Absorbs jitter in real caller audio stream |
| **Lock contention guard** | `_ai_response_lock` | Only one AI response generates at a time |
| **Town knowledge injection** | `TOWN_KNOWLEDGE` dict | Prevents LLM from inventing fake local businesses |
---
## File Map
```
ai-podcast/
├── backend/
│ ├── main.py ← FastAPI app, all endpoints, caller generation, session
│ ├── config.py ← Settings (env vars, paths)
│ └── services/
│ ├── audio.py ← Multi-channel audio I/O (sounddevice)
│ ├── caller_service.py← Phone queue, WebSocket registry, audio routing
│ ├── llm.py ← OpenRouter/Ollama with fallback chain
│ ├── news.py ← SearXNG search + caching
│ ├── tts.py ← 8 TTS providers (cloud + local)
│ └── transcription.py ← Whisper speech-to-text
├── frontend/
│ ├── index.html ← Control panel layout
│ ├── js/app.js ← UI logic, polling, event handlers
│ └── css/style.css ← Dark theme styling
├── sounds/ ← SFX files (ring, hangup, busy, etc.)
├── music/ ← Background music tracks
├── ads/ ← Ad audio files
├── website/ ← Landing page (lukeattheroost.com)
├── publish_episode.py ← Castopod episode publisher
└── run.sh ← Server launcher with restart support
```
@@ -0,0 +1,189 @@
# Real Callers + AI Follow-Up Design
## Overview
Add real phone callers to the AI Radio Show via Twilio, alongside existing AI callers. Real callers dial a phone number, wait in a hold queue, and get taken on air by the host. Three-way conversations between host, real caller, and AI caller are supported. AI follow-up callers automatically reference what real callers said.
## Requirements
- Real callers connect via Twilio phone number
- Full-duplex audio — host and caller talk simultaneously, talk over each other
- Each real caller gets their own dedicated audio channel for recording
- Three-way calls: host + real caller + AI caller all live at once
- AI caller can respond manually (host-triggered) or automatically (listens and decides when to jump in)
- AI follow-up callers reference real caller conversations via show history
- Auto follow-up mode: system picks an AI caller and connects them after a real call
- Simple hold queue — callers wait with hold music, host sees list and picks who goes on air
- Twilio webhooks exposed via Cloudflare tunnel
## Architecture
### Audio Routing (Loopback Channels)
```
Ch 1: Host mic (existing)
Ch 2: AI callers / TTS (existing)
Ch 3+: Real callers (dynamically assigned per call)
Ch N-1: Music (existing)
Ch N: SFX (existing)
```
### Call Flow — Real Caller
```
Caller dials Twilio number
→ Twilio POST /api/twilio/voice
→ TwiML response: greeting + enqueue with hold music
→ Caller waits in hold queue
→ Host sees caller in dashboard queue panel
→ Host clicks "Take Call"
→ POST /api/queue/take/{call_sid}
→ Twilio opens WebSocket to /api/twilio/stream
→ Bidirectional audio:
Caller audio → decode mulaw → dedicated Loopback channel
Host audio + AI TTS → encode mulaw → Twilio → caller hears both
→ Real-time Whisper transcription of caller audio
→ Host hangs up → call summarized → stored in show history
```
### Three-Way Call Flow
```
Host mic ──────→ Ch 1 (recording)
→ Twilio outbound (real caller hears you)
→ Whisper transcription (AI gets your words)
Real caller ──→ Ch 3+ (recording, dedicated channel)
→ Whisper transcription (AI gets their words)
→ Host headphones
AI TTS ───────→ Ch 2 (recording)
→ Twilio outbound (real caller hears AI)
→ Host headphones (already works)
```
Conversation history becomes three-party with role labels: `host`, `real_caller`, `ai_caller`.
### AI Auto-Respond Mode
When toggled on, after each real caller transcription chunk:
1. Lightweight LLM call ("should I respond?" — use fast model like Haiku)
2. If YES → full response generated → TTS → plays on AI channel + streams to Twilio
3. Cooldown (~10s) prevents rapid-fire
4. Host can override with mute button
### AI Follow-Up System
After a real caller hangs up:
1. Full transcript (host + real caller + any AI) summarized by LLM
2. Summary stored in `session.call_history`
3. Next AI caller's system prompt includes show history:
```
EARLIER IN THE SHOW:
- Dave (real caller) called about his wife leaving after 12 years.
He got emotional about his kids.
- Jasmine called about her boss hitting on her at work.
You can reference these if it feels natural. Don't force it.
```
**Host-triggered (default):** Click any AI caller as normal. They already have show context.
**Auto mode:** After real caller hangs up, system waits ~5-10s, picks a fitting AI caller via short LLM call, biases their background generation toward the topic, auto-connects.
## Backend Changes
### New Module: `backend/services/twilio_service.py`
Manages Twilio integration:
- WebSocket handler for Media Streams (decode/encode mulaw 8kHz ↔ PCM)
- Call queue state (waiting callers, SIDs, timestamps, assigned channels)
- Channel pool management (allocate/release Loopback channels for real callers)
- Outbound audio mixing (host + AI TTS → mulaw → Twilio)
- Methods: `take_call()`, `hangup_real_caller()`, `get_queue()`, `send_audio_to_caller()`
### New Endpoints
```python
# Twilio webhooks
POST /api/twilio/voice # Incoming call → TwiML (greet + enqueue)
POST /api/twilio/hold-music # Hold music TwiML for waiting callers
WS /api/twilio/stream # Media Streams WebSocket (bidirectional audio)
# Host controls
GET /api/queue # List waiting callers (number, wait time)
POST /api/queue/take/{call_sid} # Dequeue caller → start media stream
POST /api/queue/drop/{call_sid} # Drop caller from queue
# AI follow-up
POST /api/followup/generate # Summarize last real call, trigger AI follow-up
```
### Session Model Changes
```python
class CallRecord:
caller_type: str # "ai" or "real"
caller_name: str # "Tony" or "Caller #3"
summary: str # LLM-generated summary after hangup
transcript: list[dict] # Full conversation [{role, content}]
class Session:
# Existing fields...
call_history: list[CallRecord] # All calls this episode
active_real_caller: dict | None # {call_sid, phone, channel, name}
active_ai_caller: str | None # Caller key
ai_respond_mode: str # "manual" or "auto"
auto_followup: bool # Auto-generate AI follow-up after real calls
```
Three-party conversation history uses roles: `host`, `real_caller:{name}`, `ai_caller:{name}`.
### AI Caller Prompt Changes
`get_caller_prompt()` extended to include:
- Show history from `session.call_history`
- Current real caller context (if three-way call active)
- Instructions for referencing real callers naturally
## Frontend Changes
### New: Call Queue Panel
Between callers section and chat. Shows waiting real callers with phone number and wait time. "Take Call" and "Drop" buttons per caller. Polls `/api/queue` every few seconds.
### Modified: Active Call Indicator
Shows real caller and AI caller simultaneously when both active:
- Real caller: name, channel number, call duration, hang up button
- AI caller: name, Manual/Auto toggle, "Let [name] respond" button (manual mode)
- Auto Follow-Up checkbox
### Modified: Chat Log
Three-party with visual distinction:
- Host messages: existing style
- Real caller: labeled "Dave (caller)", distinct color
- AI caller: labeled "Tony (AI)", distinct color
### Modified: Caller Grid
When real caller is active, clicking an AI caller adds them as third party instead of starting fresh call. Indicator shows which AI callers have been on the show this session.
## Dependencies
- `twilio` Python package (for TwiML generation, REST API)
- Twilio account with phone number (~$1.15/mo + per-minute)
- Cloudflare tunnel for exposing webhook endpoints
- `audioop` or equivalent for mulaw encode/decode (stdlib in Python 3.11)
## Configuration
New env vars in `.env`:
```
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_PHONE_NUMBER=+1...
TWILIO_WEBHOOK_BASE_URL=https://your-tunnel.cloudflare.com
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
# SignalWire Phone Call-In Design
## Goal
Replace browser-based WebSocket call-in with real phone calls via SignalWire. Callers dial 208-439-5853 and enter the show queue.
## Architecture
SignalWire handles PSTN connectivity. When a call comes in, SignalWire hits our webhook, we return XML telling it to open a bidirectional WebSocket stream with L16@16kHz audio. The audio flows through our existing pipeline — same queue, channel allocation, transcription, host mic streaming, and TTS streaming.
## Call Flow
1. Caller dials 208-439-5853
2. SignalWire hits `POST /api/signalwire/voice` (via Cloudflare tunnel)
3. We return `<Connect><Stream codec="L16@16000h">` XML
4. SignalWire opens WebSocket to `/api/signalwire/stream`
5. Caller enters queue — host sees phone number on dashboard
6. Host takes call — audio flows bidirectionally
7. Host hangs up — we call SignalWire REST API to end the phone call
## Audio Path
```
Phone → PSTN → SignalWire → WebSocket (base64 L16 JSON) → Our server
Our server → WebSocket (base64 L16 JSON) → SignalWire → PSTN → Phone
```
## SignalWire WebSocket Protocol
Incoming: `{"event": "media", "media": {"payload": "<base64 L16 PCM 16kHz>"}}`
Outgoing: `{"event": "media", "media": {"payload": "<base64 L16 PCM 16kHz>"}}`
Start: `{"event": "start", "start": {"streamSid": "...", "callSid": "..."}}`
Stop: `{"event": "stop"}`
## What Changes
- Remove: browser call-in page, browser WebSocket handler
- Add: SignalWire webhook + WebSocket handler, hangup via REST API
- Modify: CallerService (name→phone, base64 JSON encoding for send), dashboard (show phone number)
- Unchanged: AudioService, queue logic, transcription, TTS streaming, three-way calls
## Config
```
SIGNALWIRE_PROJECT_ID=8eb54732-ade3-4487-8b40-ecd2cd680df7
SIGNALWIRE_SPACE=macneil-media-group-llc.signalwire.com
SIGNALWIRE_TOKEN=PT9c9b61f44ee49914c614fed32aa5c3d7b9372b5199d81dec
SIGNALWIRE_PHONE=+12084395853
```
Webhook URL: `https://radioshow.macneilmediagroup.com/api/signalwire/voice`
No SDK needed — httpx for the one REST call (hangup).
@@ -0,0 +1,855 @@
# SignalWire Phone Call-In Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Replace browser-based WebSocket call-in with real phone calls via SignalWire (208-439-5853).
**Architecture:** SignalWire hits our webhook on inbound calls, we return XML to open a bidirectional WebSocket stream with L16@16kHz audio. The existing queue, channel allocation, transcription, host mic streaming, and TTS streaming are reused — only the WebSocket message format changes (base64 JSON instead of raw binary).
**Tech Stack:** Python/FastAPI, SignalWire Compatibility API (LaML XML + WebSocket), httpx for REST calls, existing audio pipeline.
---
## Task 1: Add SignalWire Config
**Files:**
- Modify: `backend/config.py`
- Modify: `.env`
**Step 1: Add SignalWire settings to config.py**
In `backend/config.py`, add these fields to the `Settings` class after the existing API keys block (after line 16):
```python
# SignalWire
signalwire_project_id: str = os.getenv("SIGNALWIRE_PROJECT_ID", "")
signalwire_space: str = os.getenv("SIGNALWIRE_SPACE", "")
signalwire_token: str = os.getenv("SIGNALWIRE_TOKEN", "")
signalwire_phone: str = os.getenv("SIGNALWIRE_PHONE", "")
```
**Step 2: Add SignalWire vars to .env**
Append to `.env`:
```
# SignalWire
SIGNALWIRE_PROJECT_ID=8eb54732-ade3-4487-8b40-ecd2cd680df7
SIGNALWIRE_SPACE=macneil-media-group-llc.signalwire.com
SIGNALWIRE_TOKEN=PT9c9b61f44ee49914c614fed32aa5c3d7b9372b5199d81dec
SIGNALWIRE_PHONE=+12084395853
```
**Step 3: Verify config loads**
```bash
cd /Users/lukemacneil/ai-podcast && python -c "from backend.config import settings; print(settings.signalwire_space)"
```
Expected: `macneil-media-group-llc.signalwire.com`
**Step 4: Commit**
```bash
git add backend/config.py .env
git commit -m "Add SignalWire configuration"
```
---
## Task 2: Update CallerService for SignalWire Protocol
**Files:**
- Modify: `backend/services/caller_service.py`
The CallerService currently sends raw binary PCM frames. SignalWire needs base64-encoded L16 PCM wrapped in JSON. Also swap `name` field to `phone` since callers now have phone numbers.
**Step 1: Update queue to use `phone` instead of `name`**
In `caller_service.py`, make these changes:
1. Update docstring (line 1): `"""Phone caller queue and audio stream service"""`
2. In `add_to_queue` (line 24): Change parameter `name` to `phone`, and update the dict:
```python
def add_to_queue(self, caller_id: str, phone: str):
with self._lock:
self._queue.append({
"caller_id": caller_id,
"phone": phone,
"queued_at": time.time(),
})
print(f"[Caller] {phone} added to queue (ID: {caller_id})")
```
3. In `get_queue` (line 38): Return `phone` instead of `name`:
```python
def get_queue(self) -> list[dict]:
now = time.time()
with self._lock:
return [
{
"caller_id": c["caller_id"],
"phone": c["phone"],
"wait_time": int(now - c["queued_at"]),
}
for c in self._queue
]
```
4. In `take_call` (line 62): Use `phone` instead of `name`:
```python
def take_call(self, caller_id: str) -> dict:
caller = None
with self._lock:
for c in self._queue:
if c["caller_id"] == caller_id:
caller = c
break
if caller:
self._queue = [c for c in self._queue if c["caller_id"] != caller_id]
if not caller:
raise ValueError(f"Caller {caller_id} not in queue")
channel = self.allocate_channel()
self._caller_counter += 1
phone = caller["phone"]
call_info = {
"caller_id": caller_id,
"phone": phone,
"channel": channel,
"started_at": time.time(),
}
self.active_calls[caller_id] = call_info
print(f"[Caller] {phone} taken on air — channel {channel}")
return call_info
```
5. In `hangup` (line 89): Use `phone` instead of `name`:
```python
def hangup(self, caller_id: str):
call_info = self.active_calls.pop(caller_id, None)
if call_info:
self.release_channel(call_info["channel"])
print(f"[Caller] {call_info['phone']} hung up — channel {call_info['channel']} released")
self._websockets.pop(caller_id, None)
```
**Step 2: Update `send_audio_to_caller` for SignalWire JSON format**
Replace the existing `send_audio_to_caller` method with:
```python
async def send_audio_to_caller(self, caller_id: str, pcm_data: bytes, sample_rate: int):
"""Send small audio chunk to caller via SignalWire WebSocket.
Encodes L16 PCM as base64 JSON per SignalWire protocol.
"""
ws = self._websockets.get(caller_id)
if not ws:
return
try:
import base64
if sample_rate != 16000:
audio = np.frombuffer(pcm_data, dtype=np.int16).astype(np.float32) / 32768.0
ratio = 16000 / sample_rate
out_len = int(len(audio) * ratio)
indices = (np.arange(out_len) / ratio).astype(int)
indices = np.clip(indices, 0, len(audio) - 1)
audio = audio[indices]
pcm_data = (audio * 32767).astype(np.int16).tobytes()
payload = base64.b64encode(pcm_data).decode('ascii')
import json
await ws.send_text(json.dumps({
"event": "media",
"media": {"payload": payload}
}))
except Exception as e:
print(f"[Caller] Failed to send audio: {e}")
```
**Step 3: Update `stream_audio_to_caller` for SignalWire JSON format**
Replace the existing `stream_audio_to_caller` method with:
```python
async def stream_audio_to_caller(self, caller_id: str, pcm_data: bytes, sample_rate: int):
"""Stream large audio (TTS) to caller in real-time chunks via SignalWire WebSocket."""
ws = self._websockets.get(caller_id)
if not ws:
return
self.streaming_tts = True
try:
import base64
import json
audio = np.frombuffer(pcm_data, dtype=np.int16).astype(np.float32) / 32768.0
if sample_rate != 16000:
ratio = 16000 / sample_rate
out_len = int(len(audio) * ratio)
indices = (np.arange(out_len) / ratio).astype(int)
indices = np.clip(indices, 0, len(audio) - 1)
audio = audio[indices]
chunk_samples = 960
for i in range(0, len(audio), chunk_samples):
if caller_id not in self._websockets:
break
chunk = audio[i:i + chunk_samples]
pcm_chunk = (chunk * 32767).astype(np.int16).tobytes()
payload = base64.b64encode(pcm_chunk).decode('ascii')
await ws.send_text(json.dumps({
"event": "media",
"media": {"payload": payload}
}))
await asyncio.sleep(0.055)
except Exception as e:
print(f"[Caller] Failed to stream audio: {e}")
finally:
self.streaming_tts = False
```
**Step 4: Remove `notify_caller` and `disconnect_caller` methods**
These sent browser-specific JSON control messages. SignalWire callers are disconnected via REST API (handled in main.py). Delete methods `notify_caller` (line 168) and `disconnect_caller` (line 175). They will be replaced with a REST-based hangup in Task 4.
**Step 5: Add `call_sid` tracking for SignalWire call hangup**
Add a dict to track SignalWire call SIDs so we can end calls via REST:
In `__init__`, after `self._websockets` line, add:
```python
self._call_sids: dict[str, str] = {} # caller_id -> SignalWire callSid
```
Add methods:
```python
def register_call_sid(self, caller_id: str, call_sid: str):
"""Track SignalWire callSid for a caller"""
self._call_sids[caller_id] = call_sid
def get_call_sid(self, caller_id: str) -> str | None:
"""Get SignalWire callSid for a caller"""
return self._call_sids.get(caller_id)
def unregister_call_sid(self, caller_id: str):
"""Remove callSid tracking"""
self._call_sids.pop(caller_id, None)
```
In `reset`, also clear `self._call_sids`:
```python
self._call_sids.clear()
```
In `hangup`, also clean up call_sid:
```python
self._call_sids.pop(caller_id, None)
```
**Step 6: Run existing tests**
```bash
cd /Users/lukemacneil/ai-podcast && python -m pytest tests/test_caller_service.py -v
```
Tests will likely need updates due to `name``phone` rename. Fix any failures.
**Step 7: Commit**
```bash
git add backend/services/caller_service.py
git commit -m "Update CallerService for SignalWire protocol"
```
---
## Task 3: Add SignalWire Voice Webhook
**Files:**
- Modify: `backend/main.py`
**Step 1: Add the voice webhook endpoint**
Add after the existing route definitions (after line 421), replacing the `/call-in` route:
```python
# --- SignalWire Endpoints ---
from fastapi import Request, Response
@app.post("/api/signalwire/voice")
async def signalwire_voice_webhook(request: Request):
"""Handle inbound call from SignalWire — return XML to start bidirectional stream"""
form = await request.form()
caller_phone = form.get("From", "Unknown")
call_sid = form.get("CallSid", "")
print(f"[SignalWire] Inbound call from {caller_phone} (CallSid: {call_sid})")
# Build WebSocket URL from the request
ws_scheme = "wss"
host = request.headers.get("host", "radioshow.macneilmediagroup.com")
stream_url = f"{ws_scheme}://{host}/api/signalwire/stream"
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Connect>
<Stream url="{stream_url}" codec="L16@16000h">
<Parameter name="caller_phone" value="{caller_phone}"/>
<Parameter name="call_sid" value="{call_sid}"/>
</Stream>
</Connect>
</Response>"""
return Response(content=xml, media_type="application/xml")
```
**Step 2: Remove the `/call-in` route**
Delete these lines (around line 419-421):
```python
@app.get("/call-in")
async def call_in_page():
return FileResponse(frontend_dir / "call-in.html")
```
**Step 3: Verify server starts**
```bash
cd /Users/lukemacneil/ai-podcast && python -c "from backend.main import app; print('OK')"
```
**Step 4: Commit**
```bash
git add backend/main.py
git commit -m "Add SignalWire voice webhook, remove call-in route"
```
---
## Task 4: Add SignalWire WebSocket Stream Handler
**Files:**
- Modify: `backend/main.py`
This replaces the browser caller WebSocket handler at `/api/caller/stream`.
**Step 1: Replace the browser WebSocket handler**
Delete the entire `caller_audio_stream` function (the `@app.websocket("/api/caller/stream")` handler, lines 807-887).
Add the new SignalWire WebSocket handler:
```python
@app.websocket("/api/signalwire/stream")
async def signalwire_audio_stream(websocket: WebSocket):
"""Handle SignalWire bidirectional audio stream"""
await websocket.accept()
caller_id = str(uuid.uuid4())[:8]
caller_phone = "Unknown"
call_sid = ""
audio_buffer = bytearray()
CHUNK_DURATION_S = 3
SAMPLE_RATE = 16000
chunk_samples = CHUNK_DURATION_S * SAMPLE_RATE
stream_started = False
try:
while True:
raw = await websocket.receive_text()
msg = json.loads(raw)
event = msg.get("event")
if event == "start":
# Extract caller info from stream parameters
params = {}
for p in msg.get("start", {}).get("customParameters", {}):
pass
# customParameters comes as a dict
custom = msg.get("start", {}).get("customParameters", {})
caller_phone = custom.get("caller_phone", "Unknown")
call_sid = custom.get("call_sid", "")
stream_started = True
print(f"[SignalWire WS] Stream started: {caller_phone} (CallSid: {call_sid})")
# Add to queue and register
caller_service.add_to_queue(caller_id, caller_phone)
caller_service.register_websocket(caller_id, websocket)
if call_sid:
caller_service.register_call_sid(caller_id, call_sid)
elif event == "media" and stream_started:
# Decode base64 L16 PCM audio
import base64
payload = msg.get("media", {}).get("payload", "")
if not payload:
continue
pcm_data = base64.b64decode(payload)
# Only process audio if caller is on air
call_info = caller_service.active_calls.get(caller_id)
if not call_info:
continue
audio_buffer.extend(pcm_data)
# Route to configured live caller Loopback channel
audio_service.route_real_caller_audio(pcm_data, SAMPLE_RATE)
# Transcribe when we have enough audio
if len(audio_buffer) >= chunk_samples * 2:
pcm_chunk = bytes(audio_buffer[:chunk_samples * 2])
audio_buffer = audio_buffer[chunk_samples * 2:]
asyncio.create_task(
_handle_real_caller_transcription(caller_id, pcm_chunk, SAMPLE_RATE)
)
elif event == "stop":
print(f"[SignalWire WS] Stream stopped: {caller_phone}")
break
except WebSocketDisconnect:
print(f"[SignalWire WS] Disconnected: {caller_id} ({caller_phone})")
except Exception as e:
print(f"[SignalWire WS] Error: {e}")
finally:
caller_service.unregister_websocket(caller_id)
caller_service.unregister_call_sid(caller_id)
caller_service.remove_from_queue(caller_id)
if caller_id in caller_service.active_calls:
caller_service.hangup(caller_id)
if session.active_real_caller and session.active_real_caller.get("caller_id") == caller_id:
session.active_real_caller = None
if len(caller_service.active_calls) == 0:
audio_service.stop_host_stream()
if audio_buffer:
asyncio.create_task(
_handle_real_caller_transcription(caller_id, bytes(audio_buffer), SAMPLE_RATE)
)
```
**Step 2: Commit**
```bash
git add backend/main.py
git commit -m "Add SignalWire WebSocket stream handler, remove browser handler"
```
---
## Task 5: Update Hangup and Queue Endpoints for SignalWire
**Files:**
- Modify: `backend/main.py`
When the host hangs up or drops a caller, we need to end the actual phone call via SignalWire's REST API.
**Step 1: Add SignalWire hangup helper**
Add this function near the top of `main.py` (after imports):
```python
async def _signalwire_end_call(call_sid: str):
"""End a phone call via SignalWire REST API"""
if not call_sid or not settings.signalwire_space:
return
try:
url = f"https://{settings.signalwire_space}/api/laml/2010-04-01/Accounts/{settings.signalwire_project_id}/Calls/{call_sid}"
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.post(
url,
data={"Status": "completed"},
auth=(settings.signalwire_project_id, settings.signalwire_token),
)
print(f"[SignalWire] End call {call_sid}: {response.status_code}")
except Exception as e:
print(f"[SignalWire] Failed to end call {call_sid}: {e}")
```
Also add `import httpx` at the top of main.py if not already present.
**Step 2: Update `take_call_from_queue`**
In the `take_call_from_queue` endpoint, update `name` references to `phone`:
```python
@app.post("/api/queue/take/{caller_id}")
async def take_call_from_queue(caller_id: str):
"""Take a caller off hold and put them on air"""
try:
call_info = caller_service.take_call(caller_id)
except ValueError as e:
raise HTTPException(404, str(e))
session.active_real_caller = {
"caller_id": call_info["caller_id"],
"channel": call_info["channel"],
"phone": call_info["phone"],
}
# Start host mic streaming if this is the first real caller
if len(caller_service.active_calls) == 1:
_start_host_audio_sender()
audio_service.start_host_stream(_host_audio_sync_callback)
return {
"status": "on_air",
"caller": call_info,
}
```
Note: The `notify_caller` call is removed — SignalWire callers don't need a JSON status message, they're already connected via the phone.
**Step 3: Update `drop_from_queue`**
End the phone call when dropping:
```python
@app.post("/api/queue/drop/{caller_id}")
async def drop_from_queue(caller_id: str):
"""Drop a caller from the queue"""
call_sid = caller_service.get_call_sid(caller_id)
caller_service.remove_from_queue(caller_id)
if call_sid:
await _signalwire_end_call(call_sid)
return {"status": "dropped"}
```
**Step 4: Update `hangup_real_caller`**
End the phone call when hanging up:
```python
@app.post("/api/hangup/real")
async def hangup_real_caller():
"""Hang up on real caller — disconnect immediately, summarize in background"""
if not session.active_real_caller:
raise HTTPException(400, "No active real caller")
caller_id = session.active_real_caller["caller_id"]
caller_phone = session.active_real_caller["phone"]
conversation_snapshot = list(session.conversation)
auto_followup_enabled = session.auto_followup
# End the phone call via SignalWire
call_sid = caller_service.get_call_sid(caller_id)
caller_service.hangup(caller_id)
if call_sid:
asyncio.create_task(_signalwire_end_call(call_sid))
# Stop host streaming if no more active callers
if len(caller_service.active_calls) == 0:
audio_service.stop_host_stream()
session.active_real_caller = None
# Play hangup sound in background
import threading
hangup_sound = settings.sounds_dir / "hangup.wav"
if hangup_sound.exists():
threading.Thread(target=audio_service.play_sfx, args=(str(hangup_sound),), daemon=True).start()
# Summarize and store history in background
asyncio.create_task(
_summarize_real_call(caller_phone, conversation_snapshot, auto_followup_enabled)
)
return {
"status": "disconnected",
"caller": caller_phone,
}
```
**Step 5: Update `_handle_real_caller_transcription`**
Change `caller_name` to `caller_phone`:
```python
async def _handle_real_caller_transcription(caller_id: str, pcm_data: bytes, sample_rate: int):
"""Transcribe a chunk of real caller audio and add to conversation"""
call_info = caller_service.active_calls.get(caller_id)
if not call_info:
return
text = await transcribe_audio(pcm_data, source_sample_rate=sample_rate)
if not text or not text.strip():
return
caller_phone = call_info["phone"]
print(f"[Real Caller] {caller_phone}: {text}")
session.add_message(f"real_caller:{caller_phone}", text)
if session.ai_respond_mode == "auto" and session.current_caller_key:
asyncio.create_task(_check_ai_auto_respond(text, caller_phone))
```
**Step 6: Update `_summarize_real_call`**
Change `caller_name` parameter to `caller_phone`:
```python
async def _summarize_real_call(caller_phone: str, conversation: list, auto_followup_enabled: bool):
"""Background task: summarize call and store in history"""
summary = ""
if conversation:
transcript_text = "\n".join(
f"{msg['role']}: {msg['content']}" for msg in conversation
)
summary = await llm_service.generate(
messages=[{"role": "user", "content": f"Summarize this radio show call in 1-2 sentences:\n{transcript_text}"}],
system_prompt="You summarize radio show conversations concisely. Focus on what the caller talked about and any emotional moments.",
)
session.call_history.append(CallRecord(
caller_type="real",
caller_name=caller_phone,
summary=summary,
transcript=conversation,
))
print(f"[Real Caller] {caller_phone} call summarized: {summary[:80]}...")
if auto_followup_enabled:
await _auto_followup(summary)
```
**Step 7: Update `_check_ai_auto_respond`**
Change parameter name from `real_caller_name` to `real_caller_phone`:
```python
async def _check_ai_auto_respond(real_caller_text: str, real_caller_phone: str):
```
(The body doesn't use the name/phone parameter in any way that needs changing.)
**Step 8: Update TTS streaming references**
In `text_to_speech` endpoint and `_check_ai_auto_respond`, the `session.active_real_caller` dict now uses `phone` instead of `name`. No code change needed for the TTS streaming since it only uses `caller_id`.
**Step 9: Verify server starts**
```bash
cd /Users/lukemacneil/ai-podcast && python -c "from backend.main import app; print('OK')"
```
**Step 10: Commit**
```bash
git add backend/main.py
git commit -m "Update hangup and queue endpoints for SignalWire REST API"
```
---
## Task 6: Update Frontend for Phone Callers
**Files:**
- Modify: `frontend/js/app.js`
- Modify: `frontend/index.html`
**Step 1: Update queue rendering in app.js**
In `renderQueue` function (around line 875), change `caller.name` to `caller.phone`:
```javascript
el.innerHTML = queue.map(caller => {
const mins = Math.floor(caller.wait_time / 60);
const secs = caller.wait_time % 60;
const waitStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
return `
<div class="queue-item">
<span class="queue-name">${caller.phone}</span>
<span class="queue-wait">waiting ${waitStr}</span>
<button class="queue-take-btn" onclick="takeCall('${caller.caller_id}')">Take Call</button>
<button class="queue-drop-btn" onclick="dropCall('${caller.caller_id}')">Drop</button>
</div>
`;
}).join('');
```
**Step 2: Update `takeCall` log message**
In `takeCall` function (around line 896), change `data.caller.name` to `data.caller.phone`:
```javascript
if (data.status === 'on_air') {
showRealCaller(data.caller);
log(`${data.caller.phone} is on air — Channel ${data.caller.channel}`);
}
```
**Step 3: Update `showRealCaller` to use phone**
In `showRealCaller` function (around line 939):
```javascript
function showRealCaller(callerInfo) {
const nameEl = document.getElementById('real-caller-name');
const chEl = document.getElementById('real-caller-channel');
if (nameEl) nameEl.textContent = callerInfo.phone;
if (chEl) chEl.textContent = `Ch ${callerInfo.channel}`;
```
**Step 4: Update index.html queue section header**
In `frontend/index.html`, change the queue section header (line 56) — remove the call-in page link:
```html
<section class="queue-section">
<h2>Incoming Calls</h2>
<div id="call-queue" class="call-queue">
```
**Step 5: Bump cache version in index.html**
Find the app.js script tag and bump the version:
```html
<script src="/js/app.js?v=13"></script>
```
**Step 6: Commit**
```bash
git add frontend/js/app.js frontend/index.html
git commit -m "Update frontend for phone caller display"
```
---
## Task 7: Remove Browser Call-In Files
**Files:**
- Delete: `frontend/call-in.html`
- Delete: `frontend/js/call-in.js`
**Step 1: Delete files**
```bash
cd /Users/lukemacneil/ai-podcast && rm frontend/call-in.html frontend/js/call-in.js
```
**Step 2: Commit**
```bash
git add frontend/call-in.html frontend/js/call-in.js
git commit -m "Remove browser call-in page"
```
---
## Task 8: Update Tests
**Files:**
- Modify: `tests/test_caller_service.py`
**Step 1: Update tests for `name` → `phone` rename**
Throughout `test_caller_service.py`, change:
- `add_to_queue(caller_id, "TestName")``add_to_queue(caller_id, "+15551234567")`
- `caller["name"]``caller["phone"]`
- `call_info["name"]``call_info["phone"]`
Also remove any tests for `notify_caller` or `disconnect_caller` if they exist, since those methods were removed.
**Step 2: Run all tests**
```bash
cd /Users/lukemacneil/ai-podcast && python -m pytest tests/ -v
```
Expected: All pass.
**Step 3: Commit**
```bash
git add tests/
git commit -m "Update tests for SignalWire phone caller format"
```
---
## Task 9: Configure SignalWire Webhook and End-to-End Test
**Step 1: Start the server**
```bash
cd /Users/lukemacneil/ai-podcast && python -m uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
```
**Step 2: Verify webhook endpoint responds**
```bash
curl -X POST http://localhost:8000/api/signalwire/voice \
-d "From=+15551234567&CallSid=test123" \
-H "Content-Type: application/x-www-form-urlencoded"
```
Expected: XML response with `<Connect><Stream>` containing the WebSocket URL.
**Step 3: Verify Cloudflare tunnel is running**
```bash
curl -s https://radioshow.macneilmediagroup.com/api/server/status
```
Expected: JSON response with `"status": "running"`.
**Step 4: Configure SignalWire webhook**
In the SignalWire dashboard:
1. Go to Phone Numbers → 208-439-5853
2. Set "When a call comes in" to: `https://radioshow.macneilmediagroup.com/api/signalwire/voice`
3. Method: POST
4. Handler type: LaML Webhooks
**Step 5: Test with a real call**
Call 208-439-5853 from a phone. Expected:
1. Call connects (no ringing/hold — goes straight to stream)
2. Caller appears in queue on host dashboard with phone number
3. Host clicks "Take Call" → audio flows bidirectionally
4. Host clicks "Hang Up" → phone call ends
**Step 6: Commit any fixes needed**
```bash
git add -A
git commit -m "Final SignalWire integration fixes"
```
---
## Summary
| Task | What | Key Files |
|------|------|-----------|
| 1 | SignalWire config | `config.py`, `.env` |
| 2 | CallerService protocol update | `caller_service.py` |
| 3 | Voice webhook endpoint | `main.py` |
| 4 | WebSocket stream handler | `main.py` |
| 5 | Hangup/queue via REST API | `main.py` |
| 6 | Frontend phone display | `app.js`, `index.html` |
| 7 | Remove browser call-in | `call-in.html`, `call-in.js` |
| 8 | Update tests | `tests/` |
| 9 | Configure & test | SignalWire dashboard |
Tasks 1-5 are sequential backend. Task 6-7 are frontend (can parallel after task 5). Task 8 after task 2. Task 9 is final integration test.
+505
View File
@@ -0,0 +1,505 @@
# Clip Social Media Upload Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Generate social media descriptions/hashtags for podcast clips and upload them to Instagram Reels + YouTube Shorts via Postiz API.
**Architecture:** Two changes — (1) extend `make_clips.py` to add a second LLM call that generates descriptions + hashtags, saved as `clips-metadata.json`, (2) new `upload_clips.py` script that reads that metadata and pushes clips through the self-hosted Postiz instance at `social.lukeattheroost.com`.
**Tech Stack:** Python, OpenRouter API (Claude Sonnet), Postiz REST API, requests library (already installed)
---
### Task 1: Add `generate_social_metadata()` to `make_clips.py`
**Files:**
- Modify: `make_clips.py:231-312` (after `select_clips_with_llm`)
**Step 1: Add the function after `select_clips_with_llm`**
Add this function at line ~314 (after `select_clips_with_llm` returns):
```python
def generate_social_metadata(clips: list[dict], labeled_transcript: str,
episode_number: int | None) -> list[dict]:
"""Generate social media descriptions and hashtags for each clip."""
if not OPENROUTER_API_KEY:
print("Error: OPENROUTER_API_KEY not set in .env")
sys.exit(1)
clips_summary = "\n".join(
f'{i+1}. "{c["title"]}"{c["caption_text"]}'
for i, c in enumerate(clips)
)
episode_context = f"This is Episode {episode_number} of " if episode_number else "This is an episode of "
prompt = f"""{episode_context}the "Luke at the Roost" podcast — a late-night call-in show where AI-generated callers share stories, confessions, and hot takes with host Luke.
Here are {len(clips)} clips selected from this episode:
{clips_summary}
For each clip, generate:
1. description: A short, engaging description for social media (1-2 sentences, hook the viewer, conversational tone). Do NOT include hashtags in the description.
2. hashtags: An array of 5-8 hashtags. Always include #lukeattheroost and #podcast. Add topic-relevant and trending-style tags.
Respond with ONLY a JSON array matching the clip order:
[{{"description": "...", "hashtags": ["#tag1", "#tag2", ...]}}]"""
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
},
json={
"model": "anthropic/claude-sonnet-4-5",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 2048,
"temperature": 0.7,
},
)
if response.status_code != 200:
print(f"Error from OpenRouter: {response.text}")
return clips
content = response.json()["choices"][0]["message"]["content"].strip()
if content.startswith("```"):
content = re.sub(r"^```(?:json)?\n?", "", content)
content = re.sub(r"\n?```$", "", content)
try:
metadata = json.loads(content)
except json.JSONDecodeError as e:
print(f"Error parsing social metadata: {e}")
return clips
for i, clip in enumerate(clips):
if i < len(metadata):
clip["description"] = metadata[i].get("description", "")
clip["hashtags"] = metadata[i].get("hashtags", [])
return clips
```
**Step 2: Run existing tests to verify no breakage**
Run: `pytest tests/ -v`
Expected: All existing tests pass (this is a new function, no side effects yet)
**Step 3: Commit**
```bash
git add make_clips.py
git commit -m "Add generate_social_metadata() for clip descriptions and hashtags"
```
---
### Task 2: Integrate metadata generation + JSON save into `main()`
**Files:**
- Modify: `make_clips.py:1082-1289` (inside `main()`)
**Step 1: Add metadata generation call and JSON save**
After the LLM clip selection step (~line 1196, after the clip summary print loop), add:
```python
# Step N: Generate social media metadata
print(f"\n[{extract_step - 1}/{step_total}] Generating social media descriptions...")
clips = generate_social_metadata(clips, labeled_transcript, episode_number)
for i, clip in enumerate(clips):
if "description" in clip:
print(f" Clip {i+1}: {clip['description'][:80]}...")
print(f" {' '.join(clip.get('hashtags', []))}")
```
Note: This needs to be inserted BEFORE the audio extraction step, and the step numbering needs to be adjusted (total steps goes from 5/6 to 6/7).
At the end of `main()`, before the summary print, save the metadata JSON:
```python
# Save clips metadata for social upload
metadata_path = output_dir / "clips-metadata.json"
metadata = []
for i, clip in enumerate(clips):
slug = slugify(clip["title"])
metadata.append({
"title": clip["title"],
"clip_file": f"clip-{i+1}-{slug}.mp4",
"audio_file": f"clip-{i+1}-{slug}.mp3",
"caption_text": clip.get("caption_text", ""),
"description": clip.get("description", ""),
"hashtags": clip.get("hashtags", []),
"start_time": clip["start_time"],
"end_time": clip["end_time"],
"duration": round(clip["end_time"] - clip["start_time"], 1),
"episode_number": episode_number,
})
with open(metadata_path, "w") as f:
json.dump(metadata, f, indent=2)
print(f"\nSocial metadata: {metadata_path}")
```
**Step 2: Adjust step numbering**
The pipeline steps need to account for the new metadata step. Update `step_total` calculation:
```python
step_total = (7 if two_pass else 6)
```
And shift the extract/video step numbers up by 1.
**Step 3: Test manually**
Run: `python make_clips.py --help`
Expected: No import errors, help displays normally
**Step 4: Commit**
```bash
git add make_clips.py
git commit -m "Save clips-metadata.json with social descriptions and hashtags"
```
---
### Task 3: Create `upload_clips.py` — core structure and Postiz API helpers
**Files:**
- Create: `upload_clips.py`
**Step 1: Write the script**
```python
#!/usr/bin/env python3
"""Upload podcast clips to Instagram Reels and YouTube Shorts via Postiz.
Usage:
python upload_clips.py clips/episode-12/
python upload_clips.py clips/episode-12/ --clip 1
python upload_clips.py clips/episode-12/ --youtube-only
python upload_clips.py clips/episode-12/ --instagram-only
python upload_clips.py clips/episode-12/ --schedule "2026-02-16T10:00:00"
python upload_clips.py clips/episode-12/ --yes # skip confirmation
"""
import argparse
import json
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv
import os
load_dotenv(Path(__file__).parent / ".env")
POSTIZ_API_KEY = os.getenv("POSTIZ_API_KEY")
POSTIZ_URL = os.getenv("POSTIZ_URL", "https://social.lukeattheroost.com")
def get_api_url(path: str) -> str:
"""Build full Postiz API URL."""
base = POSTIZ_URL.rstrip("/")
# Postiz self-hosted API is at /api/public/v1 when NEXT_PUBLIC_BACKEND_URL is the app URL
# but the docs say /public/v1 relative to backend URL. Try the standard path.
return f"{base}/api/public/v1{path}"
def api_headers() -> dict:
return {
"Authorization": POSTIZ_API_KEY,
"Content-Type": "application/json",
}
def fetch_integrations() -> list[dict]:
"""Fetch connected social accounts from Postiz."""
resp = requests.get(get_api_url("/integrations"), headers=api_headers(), timeout=15)
if resp.status_code != 200:
print(f"Error fetching integrations: {resp.status_code} {resp.text[:200]}")
sys.exit(1)
return resp.json()
def find_integration(integrations: list[dict], provider: str) -> dict | None:
"""Find integration by provider name (e.g. 'instagram', 'youtube')."""
for integ in integrations:
if integ.get("providerIdentifier", "").startswith(provider):
return integ
if integ.get("provider", "").startswith(provider):
return integ
return None
def upload_file(file_path: Path) -> dict:
"""Upload a file to Postiz. Returns {id, path}."""
headers = {"Authorization": POSTIZ_API_KEY}
with open(file_path, "rb") as f:
resp = requests.post(
get_api_url("/upload"),
headers=headers,
files={"file": (file_path.name, f, "video/mp4")},
timeout=120,
)
if resp.status_code != 200:
print(f"Upload failed: {resp.status_code} {resp.text[:200]}")
return {}
return resp.json()
def create_post(integration_id: str, content: str, media: dict,
settings: dict, schedule: str | None = None) -> dict:
"""Create a post on Postiz."""
post_type = "schedule" if schedule else "now"
payload = {
"type": post_type,
"posts": [
{
"integration": {"id": integration_id},
"value": [
{
"content": content,
"image": [media] if media else [],
}
],
"settings": settings,
}
],
}
if schedule:
payload["date"] = schedule
resp = requests.post(
get_api_url("/posts"),
headers=api_headers(),
json=payload,
timeout=30,
)
if resp.status_code not in (200, 201):
print(f"Post creation failed: {resp.status_code} {resp.text[:300]}")
return {}
return resp.json()
def build_instagram_content(clip: dict) -> str:
"""Build Instagram post content: description + hashtags."""
parts = [clip.get("description", clip.get("caption_text", ""))]
hashtags = clip.get("hashtags", [])
if hashtags:
parts.append("\n\n" + " ".join(hashtags))
return "".join(parts)
def build_youtube_content(clip: dict) -> str:
"""Build YouTube description."""
parts = [clip.get("description", clip.get("caption_text", ""))]
hashtags = clip.get("hashtags", [])
if hashtags:
parts.append("\n\n" + " ".join(hashtags))
parts.append("\n\nListen to the full episode: lukeattheroost.com")
return "".join(parts)
def main():
parser = argparse.ArgumentParser(description="Upload podcast clips to social media via Postiz")
parser.add_argument("clips_dir", help="Path to clips directory (e.g. clips/episode-12/)")
parser.add_argument("--clip", "-c", type=int, help="Upload only clip N (1-indexed)")
parser.add_argument("--instagram-only", action="store_true", help="Upload to Instagram only")
parser.add_argument("--youtube-only", action="store_true", help="Upload to YouTube only")
parser.add_argument("--schedule", "-s", help="Schedule time (ISO 8601, e.g. 2026-02-16T10:00:00)")
parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
parser.add_argument("--dry-run", action="store_true", help="Show what would be uploaded without posting")
args = parser.parse_args()
if not POSTIZ_API_KEY:
print("Error: POSTIZ_API_KEY not set in .env")
sys.exit(1)
clips_dir = Path(args.clips_dir).expanduser().resolve()
metadata_path = clips_dir / "clips-metadata.json"
if not metadata_path.exists():
print(f"Error: No clips-metadata.json found in {clips_dir}")
print("Run make_clips.py first to generate clips and metadata.")
sys.exit(1)
with open(metadata_path) as f:
clips = json.load(f)
if args.clip:
if args.clip < 1 or args.clip > len(clips):
print(f"Error: Clip {args.clip} not found (have {len(clips)} clips)")
sys.exit(1)
clips = [clips[args.clip - 1]]
# Determine which platforms to post to
do_instagram = not args.youtube_only
do_youtube = not args.instagram_only
# Fetch integrations from Postiz
print("Fetching connected accounts from Postiz...")
integrations = fetch_integrations()
ig_integration = None
yt_integration = None
if do_instagram:
ig_integration = find_integration(integrations, "instagram")
if not ig_integration:
print("Warning: No Instagram account connected in Postiz")
do_instagram = False
if do_youtube:
yt_integration = find_integration(integrations, "youtube")
if not yt_integration:
print("Warning: No YouTube account connected in Postiz")
do_youtube = False
if not do_instagram and not do_youtube:
print("Error: No platforms available to upload to")
sys.exit(1)
# Show summary
platforms = []
if do_instagram:
platforms.append(f"Instagram Reels ({ig_integration.get('name', 'connected')})")
if do_youtube:
platforms.append(f"YouTube Shorts ({yt_integration.get('name', 'connected')})")
print(f"\nUploading {len(clips)} clip(s) to: {', '.join(platforms)}")
if args.schedule:
print(f"Scheduled for: {args.schedule}")
print()
for i, clip in enumerate(clips):
print(f" {i+1}. \"{clip['title']}\" ({clip['duration']:.0f}s)")
print(f" {clip.get('description', '')[:80]}")
print(f" {' '.join(clip.get('hashtags', []))}")
print()
if args.dry_run:
print("Dry run — nothing uploaded.")
return
if not args.yes:
confirm = input("Proceed? [y/N] ").strip().lower()
if confirm != "y":
print("Cancelled.")
return
# Upload each clip
for i, clip in enumerate(clips):
clip_file = clips_dir / clip["clip_file"]
if not clip_file.exists():
print(f" Clip {i+1}: Video file not found: {clip_file}")
continue
print(f"\n Clip {i+1}: \"{clip['title']}\"")
# Upload video to Postiz
print(f" Uploading {clip_file.name}...")
media = upload_file(clip_file)
if not media:
print(f" Failed to upload video, skipping")
continue
print(f" Uploaded: {media.get('path', 'ok')}")
# Post to Instagram Reels
if do_instagram:
print(f" Posting to Instagram Reels...")
content = build_instagram_content(clip)
settings = {
"__type": "instagram",
"post_type": "reel",
}
result = create_post(
ig_integration["id"], content, media, settings, args.schedule
)
if result:
print(f" Instagram: Posted!")
else:
print(f" Instagram: Failed")
# Post to YouTube Shorts
if do_youtube:
print(f" Posting to YouTube Shorts...")
content = build_youtube_content(clip)
settings = {
"__type": "youtube",
"title": clip["title"],
"type": "short",
"selfDeclaredMadeForKids": False,
"tags": [h.lstrip("#") for h in clip.get("hashtags", [])],
}
result = create_post(
yt_integration["id"], content, media, settings, args.schedule
)
if result:
print(f" YouTube: Posted!")
else:
print(f" YouTube: Failed")
print(f"\nDone!")
if __name__ == "__main__":
main()
```
**Step 2: Add `POSTIZ_API_KEY` and `POSTIZ_URL` to `.env`**
Add to `.env`:
```
POSTIZ_API_KEY=your-postiz-api-key-here
POSTIZ_URL=https://social.lukeattheroost.com
```
Get your API key from Postiz Settings page.
**Step 3: Test the script loads**
Run: `python upload_clips.py --help`
Expected: Help text displays with all flags
**Step 4: Commit**
```bash
git add upload_clips.py
git commit -m "Add upload_clips.py for posting clips to Instagram/YouTube via Postiz"
```
---
### Task 4: Test with real Postiz instance
**Step 1: Get Postiz API key**
Go to `https://social.lukeattheroost.com` → Settings → API Keys → Generate key. Add to `.env` as `POSTIZ_API_KEY`.
**Step 2: Verify integrations endpoint**
Run: `python -c "from upload_clips import *; print(json.dumps(fetch_integrations(), indent=2))"`
This confirms the API key works and shows connected Instagram/YouTube accounts. Note the integration IDs and provider identifiers — if `find_integration()` doesn't match correctly, adjust the provider string matching.
**Step 3: Dry-run with existing clips**
Run: `python upload_clips.py clips/episode-12/ --dry-run`
Expected: Shows clip summary, "Dry run — nothing uploaded."
**Step 4: Upload a single test clip**
Run: `python upload_clips.py clips/episode-12/ --clip 1 --instagram-only`
Check Postiz dashboard and Instagram to verify it posted as a Reel.
**Step 5: Commit .env update (do NOT commit the key itself)**
The `.env` is gitignored so no action needed. Just ensure the key names are documented in CLAUDE.md if desired.
+402
View File
@@ -0,0 +1,402 @@
# Idents Playback Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add an idents section that loads MP3s from `idents/` and plays them through the ads channel (ch 11), with a separate "idents" stem for post-production.
**Architecture:** Mirrors the existing ads system — dropdown + play/stop buttons, same audio channel, mutually exclusive with ads. Idents get their own stem in stem_recorder so they can be mixed independently in post-production.
**Tech Stack:** Python (FastAPI), sounddevice, librosa, vanilla JS
---
### Task 1: Add idents_dir to config
**Files:**
- Modify: `backend/config.py:46-47`
**Step 1: Add idents_dir path**
After `ads_dir` (line 46), add:
```python
idents_dir: Path = base_dir / "idents"
```
**Step 2: Create the idents directory**
```bash
mkdir -p idents
```
**Step 3: Commit**
```bash
git add backend/config.py
git commit -m "Add idents_dir to config"
```
---
### Task 2: Add "idents" stem to stem_recorder
**Files:**
- Modify: `backend/services/stem_recorder.py:10`
**Step 1: Add "idents" to STEM_NAMES**
Change line 10 from:
```python
STEM_NAMES = ["host", "caller", "music", "sfx", "ads"]
```
to:
```python
STEM_NAMES = ["host", "caller", "music", "sfx", "ads", "idents"]
```
**Step 2: Add "idents" to postprod.py STEM_NAMES**
In `postprod.py:20`, change:
```python
STEM_NAMES = ["host", "caller", "music", "sfx", "ads"]
```
to:
```python
STEM_NAMES = ["host", "caller", "music", "sfx", "ads", "idents"]
```
Also update `postprod.py:72` — the `remove_gaps` content detection line — add idents:
```python
content = stems["host"] + stems["caller"] + stems["sfx"] + stems["ads"] + stems["idents"]
```
And in `mix_stems` (line 411), add idents level:
```python
levels = {"host": 0, "caller": 0, "music": -6, "sfx": -10, "ads": 0, "idents": 0}
```
And in stereo pans (line 420):
```python
pans = {"host": 0.0, "caller": 0.15, "music": 0.0, "sfx": 0.0, "ads": 0.0, "idents": 0.0}
```
And in `match_voice_levels` (line 389), add "idents":
```python
for name in ["host", "caller", "ads", "idents"]:
```
And in gap removal limiter section (line 777-778):
```python
for name in ["ads", "sfx", "idents"]:
```
**Step 3: Commit**
```bash
git add backend/services/stem_recorder.py postprod.py
git commit -m "Add idents stem to recorder and postprod"
```
---
### Task 3: Add play_ident / stop_ident to audio service
**Files:**
- Modify: `backend/services/audio.py`
**Step 1: Add ident state vars to __init__ (after line 40)**
After the ad playback state block (lines 35-40), add:
```python
# Ident playback state
self._ident_stream: Optional[sd.OutputStream] = None
self._ident_data: Optional[np.ndarray] = None
self._ident_resampled: Optional[np.ndarray] = None
self._ident_position: int = 0
self._ident_playing: bool = False
```
**Step 2: Add play_ident method (after stop_ad, ~line 1006)**
Insert after `stop_ad` method. This is a copy of `play_ad` with:
- `_ad_*``_ident_*`
- Calls `self.stop_ad()` at the start (mutual exclusion)
- Stem recording writes to `"idents"` instead of `"ads"`
```python
def play_ident(self, file_path: str):
"""Load and play an ident file once (no loop) on the ad channel"""
import librosa
path = Path(file_path)
if not path.exists():
print(f"Ident file not found: {file_path}")
return
self.stop_ident()
self.stop_ad()
try:
audio, sr = librosa.load(str(path), sr=self.output_sample_rate, mono=True)
self._ident_data = audio.astype(np.float32)
except Exception as e:
print(f"Failed to load ident: {e}")
return
self._ident_playing = True
self._ident_position = 0
if self.output_device is None:
num_channels = 2
device = None
device_sr = self.output_sample_rate
channel_idx = 0
else:
device_info = sd.query_devices(self.output_device)
num_channels = device_info['max_output_channels']
device_sr = int(device_info['default_samplerate'])
device = self.output_device
channel_idx = min(self.ad_channel, num_channels) - 1
if self.output_sample_rate != device_sr:
self._ident_resampled = librosa.resample(
self._ident_data, orig_sr=self.output_sample_rate, target_sr=device_sr
).astype(np.float32)
else:
self._ident_resampled = self._ident_data
def callback(outdata, frames, time_info, status):
outdata[:] = 0
if not self._ident_playing or self._ident_resampled is None:
return
remaining = len(self._ident_resampled) - self._ident_position
if remaining >= frames:
chunk = self._ident_resampled[self._ident_position:self._ident_position + frames]
outdata[:, channel_idx] = chunk
if self.stem_recorder:
self.stem_recorder.write_sporadic("idents", chunk.copy(), device_sr)
self._ident_position += frames
else:
if remaining > 0:
outdata[:remaining, channel_idx] = self._ident_resampled[self._ident_position:]
self._ident_playing = False
try:
self._ident_stream = sd.OutputStream(
device=device,
channels=num_channels,
samplerate=device_sr,
dtype=np.float32,
callback=callback,
blocksize=2048
)
self._ident_stream.start()
print(f"Ident playback started on ch {self.ad_channel} @ {device_sr}Hz")
except Exception as e:
print(f"Ident playback error: {e}")
self._ident_playing = False
def stop_ident(self):
"""Stop ident playback"""
self._ident_playing = False
if self._ident_stream:
self._ident_stream.stop()
self._ident_stream.close()
self._ident_stream = None
self._ident_position = 0
```
**Step 3: Add `self.stop_ident()` to top of play_ad (line 935)**
In `play_ad`, after `self.stop_ad()` (line 935), add:
```python
self.stop_ident()
```
**Step 4: Commit**
```bash
git add backend/services/audio.py
git commit -m "Add play_ident/stop_ident to audio service"
```
---
### Task 4: Add idents API endpoints
**Files:**
- Modify: `backend/main.py` (after ads endpoints, ~line 4362)
**Step 1: Add IDENT_DISPLAY_NAMES and endpoints**
Insert after the ads stop endpoint (line 4362):
```python
# --- Idents Endpoints ---
IDENT_DISPLAY_NAMES = {}
@app.get("/api/idents")
async def get_idents():
"""Get available ident tracks, shuffled"""
ident_list = []
if settings.idents_dir.exists():
for ext in ['*.wav', '*.mp3', '*.flac']:
for f in settings.idents_dir.glob(ext):
ident_list.append({
"name": IDENT_DISPLAY_NAMES.get(f.stem, f.stem),
"file": f.name,
"path": str(f)
})
random.shuffle(ident_list)
return {"idents": ident_list}
@app.post("/api/idents/play")
async def play_ident(request: MusicRequest):
"""Play an ident once on the ad channel (ch 11)"""
ident_path = settings.idents_dir / request.track
if not ident_path.exists():
raise HTTPException(404, "Ident not found")
if audio_service._music_playing:
audio_service.stop_music(fade_duration=1.0)
await asyncio.sleep(1.1)
audio_service.play_ident(str(ident_path))
return {"status": "playing", "track": request.track}
@app.post("/api/idents/stop")
async def stop_ident():
"""Stop ident playback"""
audio_service.stop_ident()
return {"status": "stopped"}
```
**Step 2: Commit**
```bash
git add backend/main.py
git commit -m "Add idents API endpoints"
```
---
### Task 5: Add idents UI section and JS functions
**Files:**
- Modify: `frontend/index.html:113` (after ads section)
- Modify: `frontend/js/app.js`
**Step 1: Add Idents HTML section**
After the Ads section closing `</section>` (line 113), add:
```html
<!-- Idents -->
<section class="music-section">
<h2>Idents</h2>
<select id="ident-select"></select>
<div class="music-controls">
<button id="ident-play-btn">Play Ident</button>
<button id="ident-stop-btn">Stop</button>
</div>
</section>
```
**Step 2: Add loadIdents, playIdent, stopIdent to app.js**
After `stopAd()` function (~line 773), add:
```javascript
async function loadIdents() {
try {
const res = await fetch('/api/idents');
const data = await res.json();
const idents = data.idents || [];
const select = document.getElementById('ident-select');
if (!select) return;
const previousValue = select.value;
select.innerHTML = '';
idents.forEach(ident => {
const option = document.createElement('option');
option.value = ident.file;
option.textContent = ident.name;
select.appendChild(option);
});
if (previousValue && [...select.options].some(o => o.value === previousValue)) {
select.value = previousValue;
}
console.log('Loaded', idents.length, 'idents');
} catch (err) {
console.error('loadIdents error:', err);
}
}
async function playIdent() {
await loadIdents();
const select = document.getElementById('ident-select');
const track = select?.value;
if (!track) return;
await fetch('/api/idents/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ track, action: 'play' })
});
}
async function stopIdent() {
await fetch('/api/idents/stop', { method: 'POST' });
}
```
**Step 3: Add event listeners in initEventListeners**
After the ads event listeners (line 190), add:
```javascript
// Idents
document.getElementById('ident-play-btn')?.addEventListener('click', playIdent);
document.getElementById('ident-stop-btn')?.addEventListener('click', stopIdent);
```
**Step 4: Add loadIdents() to DOMContentLoaded init**
After `await loadAds();` (line 59), add:
```javascript
await loadIdents();
```
**Step 5: Bump cache buster on app.js script tag**
In `index.html:243`, change `?v=17` to `?v=18`.
**Step 6: Commit**
```bash
git add frontend/index.html frontend/js/app.js
git commit -m "Add idents UI section and JS functions"
```
@@ -0,0 +1,29 @@
# Clips Page & Landing Page Redesign
## Clips Page (`/clips`)
Responsive grid gallery of podcast clips with click-to-play YouTube embeds.
**Grid:** 3 columns desktop, 2 tablet, 1 mobile. Cards use 9:16 vertical aspect ratio.
**Card pre-click:** Dark bg-light card with clip title (bold), episode label, centered orange play button, description text below. Matches site aesthetic.
**Card playing:** Click swaps card for YouTube Shorts iframe (`youtube-nocookie.com`, autoplay). Fills same 9:16 space.
**Data:** Static `website/data/clips.json` aggregated from per-episode `clips-metadata.json` files. Each entry: title, description, episode_number, optional `youtube_id`. Cards without youtube_id show no play button.
**Featured row:** Top 3 hand-picked clips displayed larger, followed by full grid below.
**Nav:** "Clips" added to hero secondary links and footer nav.
## Landing Page Improvements
**About section** (between hero and episodes): Centered text block. Show description + AI teaser line ("Part human callers, part AI-generated characters, fully unhinged advice") + "See how it works" link. No card background.
**Clips highlight** (between about and episodes): Horizontal row of 3 featured clips, same card style as clips page. "Best Clips" header with "See all clips" link.
**Final section order:** Banner → Hero → About → Featured Clips → Episodes → Testimonials → Footer
## How It Works — Reaper Video
New "Post-Production Automation" section with native `<video>` tag (mp4 on CDN). Shows Reaper automating silence removal, ad ducking, loudness normalization. Wrapped in hiw-hero-card style container.
+257 -78
View File
@@ -1,12 +1,17 @@
/* AI Radio Show - Clean CSS */
/* AI Radio Show - Control Panel */
:root {
--bg: #1a1a2e;
--bg-light: #252547;
--accent: #e94560;
--text: #fff;
--text-muted: #888;
--radius: 8px;
--bg: #1a1209;
--bg-light: #2a2015;
--bg-dark: #110c05;
--accent: #e8791d;
--accent-hover: #f59a4a;
--accent-red: #cc2222;
--accent-green: #5a8a3c;
--text: #f5f0e5;
--text-muted: #9a8b78;
--radius: 12px;
--radius-sm: 8px;
}
* {
@@ -16,7 +21,7 @@
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
@@ -38,6 +43,8 @@ header {
header h1 {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent);
}
.header-buttons {
@@ -48,10 +55,16 @@ header h1 {
header button {
background: var(--bg-light);
color: var(--text);
border: none;
border: 1px solid rgba(232, 121, 29, 0.15);
padding: 8px 16px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
}
header button:hover {
background: #3a2e1f;
border-color: rgba(232, 121, 29, 0.3);
}
.on-air-btn {
@@ -62,11 +75,14 @@ header button {
}
.on-air-btn.off {
background: #666 !important;
background: #4a3d2e !important;
border-color: transparent !important;
color: var(--text-muted) !important;
}
.on-air-btn.on {
background: #cc2222 !important;
background: var(--accent-red) !important;
border-color: var(--accent-red) !important;
animation: on-air-pulse 2s ease-in-out infinite;
}
@@ -75,8 +91,31 @@ header button {
50% { opacity: 0.7; }
}
.rec-btn {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
background: #4a3d2e !important;
color: var(--text-muted) !important;
border-color: transparent !important;
transition: background 0.2s;
}
.rec-btn.recording {
background: var(--accent-red) !important;
color: var(--text) !important;
border-color: var(--accent-red) !important;
animation: on-air-pulse 2s ease-in-out infinite;
}
.new-session-btn {
background: var(--accent) !important;
border-color: var(--accent) !important;
color: #fff !important;
}
.new-session-btn:hover {
background: var(--accent-hover) !important;
}
.session-id {
@@ -85,17 +124,29 @@ header button {
font-weight: normal;
}
.caller-background {
details.caller-background {
font-size: 0.85rem;
color: var(--text-muted);
padding: 10px;
background: var(--bg);
border-radius: var(--radius);
border-radius: var(--radius-sm);
margin-bottom: 12px;
line-height: 1.4;
}
.caller-background.hidden {
details.caller-background summary {
cursor: pointer;
padding: 8px 10px;
font-weight: bold;
color: var(--text);
font-size: 0.8rem;
}
details.caller-background > div {
padding: 0 10px 10px;
white-space: pre-wrap;
}
details.caller-background.hidden {
display: none;
}
@@ -117,10 +168,14 @@ section {
background: var(--bg-light);
padding: 16px;
border-radius: var(--radius);
border: 1px solid rgba(232, 121, 29, 0.08);
}
section h2 {
font-size: 1rem;
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 12px;
color: var(--text-muted);
}
@@ -138,7 +193,7 @@ section h2 {
color: var(--text);
border: 2px solid transparent;
padding: 10px 8px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
@@ -146,11 +201,13 @@ section h2 {
.caller-btn:hover {
border-color: var(--accent);
background: #2a1e10;
}
.caller-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.call-status {
@@ -162,13 +219,18 @@ section h2 {
.hangup-btn {
width: 100%;
background: #c0392b;
background: var(--accent-red);
color: white;
border: none;
padding: 12px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
.hangup-btn:hover {
background: #e03030;
}
.hangup-btn:disabled {
@@ -190,25 +252,26 @@ section h2 {
.chat-log {
height: 300px;
overflow-y: auto;
background: var(--bg);
border-radius: var(--radius);
background: var(--bg-dark);
border-radius: var(--radius-sm);
padding: 12px;
margin-bottom: 12px;
border: 1px solid rgba(232, 121, 29, 0.06);
}
.message {
padding: 8px 12px;
margin-bottom: 8px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
line-height: 1.4;
}
.message.host {
background: #2c5282;
background: #3a2510;
}
.message.caller {
background: #553c9a;
background: #2a1a0a;
}
.message strong {
@@ -229,7 +292,7 @@ section h2 {
color: white;
border: none;
padding: 16px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
font-size: 1rem;
font-weight: bold;
cursor: pointer;
@@ -237,11 +300,11 @@ section h2 {
}
.talk-btn:hover {
filter: brightness(1.1);
background: var(--accent-hover);
}
.talk-btn.recording {
background: #c0392b;
background: var(--accent-red);
animation: pulse 1s infinite;
}
@@ -253,10 +316,15 @@ section h2 {
.type-btn {
background: var(--bg);
color: var(--text);
border: none;
border: 1px solid rgba(232, 121, 29, 0.15);
padding: 16px 24px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
}
.type-btn:hover {
border-color: var(--accent);
}
.status {
@@ -276,11 +344,24 @@ section h2 {
padding: 10px;
background: var(--bg);
color: var(--text);
border: none;
border-radius: var(--radius);
border: 1px solid rgba(232, 121, 29, 0.15);
border-radius: var(--radius-sm);
margin-bottom: 10px;
}
.music-section select optgroup {
color: var(--accent);
font-weight: bold;
font-style: normal;
padding: 4px 0;
}
.music-section select option {
color: var(--text);
font-weight: normal;
padding: 2px 8px;
}
.music-controls {
display: flex;
gap: 8px;
@@ -290,29 +371,46 @@ section h2 {
.music-controls button {
background: var(--bg);
color: var(--text);
border: none;
border: 1px solid rgba(232, 121, 29, 0.15);
padding: 10px 16px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
}
.music-controls button:hover {
border-color: var(--accent);
background: #2a1e10;
}
.music-controls input[type="range"] {
flex: 1;
accent-color: var(--accent);
}
/* Soundboard */
.sounds-section {
grid-column: span 2;
}
@media (max-width: 700px) {
.sounds-section {
grid-column: span 1;
}
}
.soundboard {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(6, 1fr);
gap: 8px;
}
.sound-btn {
background: var(--bg);
color: var(--text);
border: none;
border: 1px solid rgba(232, 121, 29, 0.1);
padding: 12px 8px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
transition: all 0.1s;
@@ -320,6 +418,8 @@ section h2 {
.sound-btn:hover {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.sound-btn:active {
@@ -347,17 +447,19 @@ section h2 {
border-radius: var(--radius);
width: 90%;
max-width: 400px;
border: 1px solid rgba(232, 121, 29, 0.15);
}
.modal-content h2 {
margin-bottom: 16px;
color: var(--accent);
}
.modal-content h3 {
font-size: 0.9rem;
color: var(--text-muted);
margin: 16px 0 8px 0;
border-bottom: 1px solid var(--bg);
border-bottom: 1px solid rgba(232, 121, 29, 0.1);
padding-bottom: 4px;
}
@@ -411,11 +513,18 @@ section h2 {
padding: 10px;
background: var(--bg);
color: var(--text);
border: none;
border-radius: var(--radius);
border: 1px solid rgba(232, 121, 29, 0.15);
border-radius: var(--radius-sm);
margin-top: 4px;
}
.modal-content select:focus,
.modal-content input[type="text"]:focus,
.modal-content textarea:focus {
outline: none;
border-color: var(--accent);
}
.modal-buttons {
display: flex;
gap: 10px;
@@ -426,9 +535,10 @@ section h2 {
flex: 1;
padding: 12px;
border: none;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
}
.modal-buttons button:first-child {
@@ -436,25 +546,32 @@ section h2 {
color: white;
}
.modal-buttons button:first-child:hover {
background: var(--accent-hover);
}
.modal-buttons button:last-child {
background: var(--bg);
color: var(--text);
border: 1px solid rgba(232, 121, 29, 0.15);
}
.refresh-btn {
background: var(--bg);
color: var(--text-muted);
border: 1px solid var(--bg-light);
border: 1px solid rgba(232, 121, 29, 0.15);
padding: 6px 12px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
margin-top: 8px;
transition: all 0.2s;
}
.refresh-btn:hover {
background: var(--bg-light);
color: var(--text);
border-color: var(--accent);
}
.refresh-btn:disabled {
@@ -497,28 +614,29 @@ section h2 {
.server-btn {
border: none;
padding: 6px 12px;
border-radius: var(--radius);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
font-weight: bold;
transition: all 0.2s;
}
.server-btn.restart {
background: #2196F3;
background: var(--accent);
color: white;
}
.server-btn.restart:hover {
background: #1976D2;
background: var(--accent-hover);
}
.server-btn.stop {
background: #c0392b;
background: var(--accent-red);
color: white;
}
.server-btn.stop:hover {
background: #a93226;
background: #e03030;
}
.auto-scroll-label {
@@ -530,16 +648,21 @@ section h2 {
cursor: pointer;
}
.auto-scroll-label input[type="checkbox"] {
accent-color: var(--accent);
}
.server-log {
height: 200px;
overflow-y: auto;
background: #0d0d1a;
border-radius: var(--radius);
background: var(--bg-dark);
border-radius: var(--radius-sm);
padding: 12px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.75rem;
line-height: 1.5;
color: #8f8;
color: #b8a88a;
border: 1px solid rgba(232, 121, 29, 0.06);
}
.server-log .log-line {
@@ -548,52 +671,108 @@ section h2 {
}
.server-log .log-line.error {
color: #f88;
color: #e8604a;
}
.server-log .log-line.warning {
color: #ff8;
color: #e8b84a;
}
.server-log .log-line.tts {
color: #8ff;
color: var(--accent);
}
.server-log .log-line.chat {
color: #f8f;
color: var(--accent-hover);
}
/* Call Queue */
.queue-section { margin: 1rem 0; }
.call-queue { border: 1px solid #333; border-radius: 4px; padding: 0.5rem; max-height: 150px; overflow-y: auto; }
.queue-empty { color: #666; text-align: center; padding: 0.5rem; }
.queue-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid #222; }
.call-queue { border: 1px solid rgba(232, 121, 29, 0.15); border-radius: var(--radius-sm); padding: 0.5rem; max-height: 150px; overflow-y: auto; }
.queue-empty { color: var(--text-muted); text-align: center; padding: 0.5rem; }
.queue-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid rgba(232, 121, 29, 0.08); flex-wrap: wrap; }
.queue-item:last-child { border-bottom: none; }
.queue-phone { font-family: monospace; color: #4fc3f7; }
.queue-wait { color: #999; font-size: 0.85rem; flex: 1; }
.queue-take-btn { background: #2e7d32; color: white; border: none; padding: 0.25rem 0.75rem; border-radius: 3px; cursor: pointer; }
.queue-take-btn:hover { background: #388e3c; }
.queue-drop-btn { background: #c62828; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 3px; cursor: pointer; }
.queue-drop-btn:hover { background: #d32f2f; }
.queue-phone { font-family: monospace; color: var(--accent); }
.queue-wait { color: var(--text-muted); font-size: 0.85rem; flex: 1; }
.queue-take-btn { background: var(--accent-green); color: white; border: none; padding: 0.25rem 0.75rem; border-radius: var(--radius-sm); cursor: pointer; transition: background 0.2s; }
.queue-take-btn:hover { background: #6a9a4c; }
.queue-drop-btn { background: var(--accent-red); color: white; border: none; padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); cursor: pointer; transition: background 0.2s; }
.queue-drop-btn:hover { background: #e03030; }
/* Active Call Indicator */
.active-call { border: 1px solid #444; border-radius: 4px; padding: 0.75rem; margin: 0.5rem 0; background: #1a1a2e; }
.active-call { border: 1px solid rgba(232, 121, 29, 0.15); border-radius: var(--radius-sm); padding: 0.75rem; margin: 0.5rem 0; background: var(--bg); }
.caller-info { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.caller-info:last-of-type { margin-bottom: 0; }
.caller-type { font-size: 0.7rem; font-weight: bold; padding: 0.15rem 0.4rem; border-radius: 3px; text-transform: uppercase; }
.caller-type.real { background: #c62828; color: white; }
.caller-type.ai { background: #1565c0; color: white; }
.channel-badge { font-size: 0.75rem; color: #999; background: #222; padding: 0.1rem 0.4rem; border-radius: 3px; }
.call-duration { font-family: monospace; color: #4fc3f7; }
.caller-type { font-size: 0.7rem; font-weight: bold; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); text-transform: uppercase; }
.caller-type.real { background: var(--accent-red); color: white; }
.caller-type.ai { background: var(--accent); color: white; }
.channel-badge { font-size: 0.75rem; color: var(--text-muted); background: var(--bg-light); padding: 0.1rem 0.4rem; border-radius: var(--radius-sm); }
.call-duration { font-family: monospace; color: var(--accent); }
.ai-controls { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
.mode-toggle { display: flex; border: 1px solid #444; border-radius: 3px; overflow: hidden; }
.mode-btn { background: #222; color: #999; border: none; padding: 0.2rem 0.5rem; font-size: 0.75rem; cursor: pointer; }
.mode-btn.active { background: #1565c0; color: white; }
.respond-btn { background: #2e7d32; color: white; border: none; padding: 0.25rem 0.75rem; border-radius: 3px; font-size: 0.8rem; cursor: pointer; }
.mode-toggle { display: flex; border: 1px solid rgba(232, 121, 29, 0.2); border-radius: var(--radius-sm); overflow: hidden; }
.mode-btn { background: var(--bg-light); color: var(--text-muted); border: none; padding: 0.2rem 0.5rem; font-size: 0.75rem; cursor: pointer; transition: all 0.2s; }
.mode-btn.active { background: var(--accent); color: white; }
.respond-btn { background: var(--accent-green); color: white; border: none; padding: 0.25rem 0.75rem; border-radius: var(--radius-sm); font-size: 0.8rem; cursor: pointer; transition: background 0.2s; }
.respond-btn:hover { background: #6a9a4c; }
.hangup-btn.small { font-size: 0.75rem; padding: 0.2rem 0.5rem; }
.auto-followup-label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: #999; margin-top: 0.5rem; }
.auto-followup-label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: var(--text-muted); margin-top: 0.5rem; }
/* Returning Caller */
.caller-btn.returning {
border-color: var(--accent);
color: var(--accent);
}
.caller-btn.returning:hover {
border-color: var(--accent-hover);
color: var(--accent-hover);
}
.caller-btn.returning.active {
color: #fff;
}
/* Screening Badges */
.screening-badge { font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: var(--radius-sm); font-weight: bold; }
.screening-badge.screening { background: var(--accent); color: white; animation: pulse 1.5s infinite; }
.screening-badge.screened { background: var(--accent-green); color: white; }
.screening-summary { font-size: 0.8rem; color: var(--text-muted); font-style: italic; flex-basis: 100%; margin-top: 0.2rem; }
/* Three-Party Chat */
.message.real-caller { border-left: 3px solid #c62828; padding-left: 0.5rem; }
.message.ai-caller { border-left: 3px solid #1565c0; padding-left: 0.5rem; }
.message.host { border-left: 3px solid #2e7d32; padding-left: 0.5rem; }
.message.real-caller { border-left: 3px solid var(--accent-red); padding-left: 0.5rem; }
.message.ai-caller { border-left: 3px solid var(--accent); padding-left: 0.5rem; }
.message.host { border-left: 3px solid var(--accent-green); padding-left: 0.5rem; }
/* Voicemail */
.voicemail-section { margin: 1rem 0; }
.voicemail-list { border: 1px solid rgba(232, 121, 29, 0.15); border-radius: var(--radius-sm); padding: 0.5rem; max-height: 200px; overflow-y: auto; }
.voicemail-badge { background: var(--accent-red); color: white; font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.45rem; border-radius: 10px; margin-left: 0.4rem; vertical-align: middle; }
.voicemail-badge.hidden { display: none; }
.vm-item { display: flex; align-items: center; justify-content: space-between; padding: 0.4rem 0.5rem; border-bottom: 1px solid rgba(232, 121, 29, 0.08); }
.vm-item:last-child { border-bottom: none; }
.vm-item.vm-unlistened { background: rgba(232, 121, 29, 0.06); }
.vm-info { display: flex; gap: 0.6rem; align-items: center; flex: 1; min-width: 0; }
.vm-phone { font-family: monospace; color: var(--accent); font-size: 0.85rem; }
.vm-time { color: var(--text-muted); font-size: 0.8rem; }
.vm-dur { color: var(--text-muted); font-size: 0.8rem; }
.vm-actions { display: flex; gap: 0.3rem; flex-shrink: 0; }
.vm-btn { border: none; padding: 0.2rem 0.5rem; border-radius: var(--radius-sm); cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
.vm-btn.listen { background: var(--accent); color: white; }
.vm-btn.listen:hover { background: var(--accent-hover); }
.vm-btn.on-air { background: var(--accent-green); color: white; }
.vm-btn.on-air:hover { background: #6a9a4c; }
.vm-btn.save { background: #3a7bd5; color: white; }
.vm-btn.save:hover { background: #2a5db0; }
.vm-btn.delete { background: var(--accent-red); color: white; }
.vm-btn.delete:hover { background: #e03030; }
/* Listener Emails */
.email-item { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; border-bottom: 1px solid rgba(232, 121, 29, 0.08); }
.email-item:last-child { border-bottom: none; }
.email-item.vm-unlistened { background: rgba(232, 121, 29, 0.06); }
.email-header { display: flex; justify-content: space-between; align-items: center; }
.email-sender { color: var(--accent); font-size: 0.85rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.email-subject { font-size: 0.85rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.email-preview { font-size: 0.8rem; color: var(--text-muted); line-height: 1.3; }
.email-item .vm-actions { margin-top: 0.25rem; }
.email-body-expanded { margin-top: 0.4rem; padding: 0.5rem; background: rgba(232, 121, 29, 0.08); border-radius: var(--radius-sm); font-size: 0.85rem; line-height: 1.5; white-space: pre-wrap; max-height: 200px; overflow-y: auto; }
+48 -5
View File
@@ -12,7 +12,9 @@
<h1>Luke at The Roost</h1>
<div class="header-buttons">
<button id="on-air-btn" class="on-air-btn off">OFF AIR</button>
<button id="rec-btn" class="rec-btn" title="Record stems for post-production">REC</button>
<button id="new-session-btn" class="new-session-btn">New Session</button>
<button id="export-session-btn">Export</button>
<button id="settings-btn">Settings</button>
</div>
</header>
@@ -48,7 +50,10 @@
</label>
</div>
<div id="call-status" class="call-status">No active call</div>
<div id="caller-background" class="caller-background hidden"></div>
<details id="caller-background-details" class="caller-background hidden">
<summary>Caller Background</summary>
<div id="caller-background"></div>
</details>
<button id="hangup-btn" class="hangup-btn" disabled>Hang Up</button>
</section>
@@ -60,6 +65,22 @@
</div>
</section>
<!-- Voicemail -->
<section class="voicemail-section">
<h2>Voicemail <span id="voicemail-badge" class="voicemail-badge hidden">0</span></h2>
<div id="voicemail-list" class="voicemail-list">
<div class="queue-empty">No voicemails</div>
</div>
</section>
<!-- Listener Emails -->
<section class="voicemail-section">
<h2>Emails <span id="email-badge" class="voicemail-badge hidden">0</span></h2>
<div id="email-list" class="voicemail-list" style="max-height:300px">
<div class="queue-empty">No emails</div>
</div>
</section>
<!-- Chat -->
<section class="chat-section">
<div id="chat" class="chat-log"></div>
@@ -81,6 +102,26 @@
</div>
</section>
<!-- Ads -->
<section class="music-section">
<h2>Ads</h2>
<select id="ad-select"></select>
<div class="music-controls">
<button id="ad-play-btn">Play Ad</button>
<button id="ad-stop-btn">Stop</button>
</div>
</section>
<!-- Idents -->
<section class="music-section">
<h2>Idents</h2>
<select id="ident-select"></select>
<div class="music-controls">
<button id="ident-play-btn">Play Ident</button>
<button id="ident-stop-btn">Stop</button>
</div>
</section>
<!-- Sound Effects -->
<section class="sounds-section">
<h2>Sounds</h2>
@@ -128,10 +169,12 @@
</label>
</div>
<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>Music Ch <input type="number" id="music-channel" value="2" 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>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="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>Ident Ch <input type="number" id="ident-channel" value="15" min="1" max="16" class="channel-input"></label>
</div>
</div>
@@ -208,6 +251,6 @@
</div>
</div>
<script src="/js/app.js?v=15"></script>
<script src="/js/app.js?v=18"></script>
</body>
</html>
+437 -25
View File
@@ -16,18 +16,36 @@ let tracks = [];
let sounds = [];
// --- Helpers ---
function _isTyping() {
const el = document.activeElement;
if (!el) return false;
const tag = el.tagName;
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
}
// --- Safe JSON parsing ---
async function safeFetch(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
async function safeFetch(url, options = {}, timeoutMs = 30000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(timer);
if (!res.ok) {
const text = await res.text();
let detail = text;
try { detail = JSON.parse(text).detail || text; } catch {}
throw new Error(detail);
}
const text = await res.text();
let detail = text;
try { detail = JSON.parse(text).detail || text; } catch {}
throw new Error(detail);
if (!text) return {};
return JSON.parse(text);
} catch (err) {
clearTimeout(timer);
if (err.name === 'AbortError') throw new Error('Request timed out');
throw err;
}
const text = await res.text();
if (!text) return {};
return JSON.parse(text);
}
@@ -38,9 +56,15 @@ document.addEventListener('DOMContentLoaded', async () => {
await loadAudioDevices();
await loadCallers();
await loadMusic();
await loadAds();
await loadIdents();
await loadSounds();
await loadSettings();
initEventListeners();
loadVoicemails();
setInterval(loadVoicemails, 30000);
loadEmails();
setInterval(loadEmails, 30000);
log('Ready. Configure audio devices in Settings, then click a caller to start.');
console.log('AI Radio Show ready');
} catch (err) {
@@ -57,11 +81,23 @@ function initEventListeners() {
// New Session
document.getElementById('new-session-btn')?.addEventListener('click', newSession);
// On-Air toggle
// On-Air + Recording (linked — toggling one toggles both)
const onAirBtn = document.getElementById('on-air-btn');
const recBtn = document.getElementById('rec-btn');
let stemRecording = false;
function updateRecBtn(recording) {
stemRecording = recording;
if (recBtn) {
recBtn.classList.toggle('recording', recording);
recBtn.textContent = recording ? '⏺ REC' : 'REC';
}
}
if (onAirBtn) {
fetch('/api/on-air').then(r => r.json()).then(data => {
updateOnAirBtn(onAirBtn, data.on_air);
updateRecBtn(data.recording);
});
onAirBtn.addEventListener('click', async () => {
const isOn = onAirBtn.classList.contains('on');
@@ -71,10 +107,27 @@ function initEventListeners() {
body: JSON.stringify({ on_air: !isOn }),
});
updateOnAirBtn(onAirBtn, res.on_air);
log(res.on_air ? 'Show is ON AIR — accepting calls' : 'Show is OFF AIR — calls get off-air message');
updateRecBtn(res.recording);
log(res.on_air ? 'Show is ON AIR + Recording' : 'Show is OFF AIR + Recording stopped');
});
}
if (recBtn) {
recBtn.addEventListener('click', async () => {
try {
const res = await safeFetch('/api/recording/toggle', { method: 'POST' });
updateRecBtn(res.recording);
if (onAirBtn) updateOnAirBtn(onAirBtn, res.on_air);
log(res.recording ? 'Recording started + ON AIR' : 'Recording stopped + OFF AIR');
} catch (err) {
log('Recording error: ' + err.message);
}
});
}
// Export session
document.getElementById('export-session-btn')?.addEventListener('click', exportSession);
// Server controls
document.getElementById('restart-server-btn')?.addEventListener('click', restartServer);
document.getElementById('stop-server-btn')?.addEventListener('click', stopServer);
@@ -98,6 +151,20 @@ function initEventListeners() {
talkBtn.addEventListener('touchend', e => { e.preventDefault(); stopRecording(); });
}
// Spacebar push-to-talk — blur buttons so Space doesn't also trigger button click
document.addEventListener('keydown', e => {
if (e.code !== 'Space' || e.repeat || _isTyping()) return;
e.preventDefault();
// Blur any focused button so browser doesn't fire its click
if (document.activeElement?.tagName === 'BUTTON') document.activeElement.blur();
startRecording();
});
document.addEventListener('keyup', e => {
if (e.code !== 'Space' || _isTyping()) return;
e.preventDefault();
stopRecording();
});
// Type button
document.getElementById('type-btn')?.addEventListener('click', () => {
document.getElementById('type-modal')?.classList.remove('hidden');
@@ -119,6 +186,14 @@ function initEventListeners() {
document.getElementById('stop-btn')?.addEventListener('click', stopMusic);
document.getElementById('volume')?.addEventListener('input', setMusicVolume);
// Ads
document.getElementById('ad-play-btn')?.addEventListener('click', playAd);
document.getElementById('ad-stop-btn')?.addEventListener('click', stopAd);
// Idents
document.getElementById('ident-play-btn')?.addEventListener('click', playIdent);
document.getElementById('ident-stop-btn')?.addEventListener('click', stopIdent);
// Settings
document.getElementById('settings-btn')?.addEventListener('click', async () => {
document.getElementById('settings-modal')?.classList.remove('hidden');
@@ -267,12 +342,16 @@ async function loadAudioDevices() {
const liveCallerCh = document.getElementById('live-caller-channel');
const musicCh = document.getElementById('music-channel');
const sfxCh = document.getElementById('sfx-channel');
const adCh = document.getElementById('ad-channel');
const identCh = document.getElementById('ident-channel');
if (inputCh) inputCh.value = settings.input_channel || 1;
if (callerCh) callerCh.value = settings.caller_channel || 1;
if (liveCallerCh) liveCallerCh.value = settings.live_caller_channel || 9;
if (musicCh) musicCh.value = settings.music_channel || 2;
if (sfxCh) sfxCh.value = settings.sfx_channel || 3;
if (adCh) adCh.value = settings.ad_channel || 11;
if (identCh) identCh.value = settings.ident_channel || 15;
// Phone filter setting
const phoneFilterEl = document.getElementById('phone-filter');
@@ -296,6 +375,8 @@ async function saveAudioDevices() {
const liveCallerChannel = document.getElementById('live-caller-channel')?.value;
const musicChannel = document.getElementById('music-channel')?.value;
const sfxChannel = document.getElementById('sfx-channel')?.value;
const adChannel = document.getElementById('ad-channel')?.value;
const identChannel = document.getElementById('ident-channel')?.value;
const phoneFilterChecked = document.getElementById('phone-filter')?.checked ?? false;
await fetch('/api/audio/settings', {
@@ -309,6 +390,8 @@ async function saveAudioDevices() {
live_caller_channel: liveCallerChannel ? parseInt(liveCallerChannel) : 9,
music_channel: musicChannel ? parseInt(musicChannel) : 2,
sfx_channel: sfxChannel ? parseInt(sfxChannel) : 3,
ad_channel: adChannel ? parseInt(adChannel) : 11,
ident_channel: identChannel ? parseInt(identChannel) : 15,
phone_filter: phoneFilterChecked
})
});
@@ -333,7 +416,8 @@ async function loadCallers() {
data.callers.forEach(caller => {
const btn = document.createElement('button');
btn.className = 'caller-btn';
btn.textContent = caller.name;
if (caller.returning) btn.classList.add('returning');
btn.textContent = caller.returning ? `\u2605 ${caller.name}` : caller.name;
btn.dataset.key = caller.key;
btn.addEventListener('click', () => startCall(caller.key, caller.name));
grid.appendChild(btn);
@@ -378,11 +462,12 @@ async function startCall(key, name) {
if (aiInfo) aiInfo.classList.remove('hidden');
if (aiName) aiName.textContent = name;
// Show caller background
// Show caller background in disclosure triangle
const bgDetails = document.getElementById('caller-background-details');
const bgEl = document.getElementById('caller-background');
if (bgEl && data.background) {
if (bgDetails && bgEl && data.background) {
bgEl.textContent = data.background;
bgEl.classList.remove('hidden');
bgDetails.classList.remove('hidden');
}
document.querySelectorAll('.caller-btn').forEach(btn => {
@@ -406,8 +491,8 @@ async function newSession() {
conversationSince = 0;
// Hide caller background
const bgEl = document.getElementById('caller-background');
if (bgEl) bgEl.classList.add('hidden');
const bgDetails = document.getElementById('caller-background-details');
if (bgDetails) bgDetails.classList.add('hidden');
// Reload callers to get new session ID
await loadCallers();
@@ -433,8 +518,8 @@ async function hangup() {
document.querySelectorAll('.caller-btn').forEach(btn => btn.classList.remove('active'));
// Hide caller background
const bgEl = document.getElementById('caller-background');
if (bgEl) bgEl.classList.add('hidden');
const bgDetails2 = document.getElementById('caller-background-details');
if (bgDetails2) bgDetails2.classList.add('hidden');
// Hide AI caller indicator
document.getElementById('ai-caller-info')?.classList.add('hidden');
@@ -581,11 +666,37 @@ async function loadMusic() {
const previousValue = select.value;
select.innerHTML = '';
tracks.forEach((track, i) => {
const option = document.createElement('option');
option.value = track.file;
option.textContent = track.name;
select.appendChild(option);
// Group tracks by genre
const genres = {};
tracks.forEach(track => {
const genre = track.genre || 'Other';
if (!genres[genre]) genres[genre] = [];
genres[genre].push(track);
});
// Sort genre names, but put "Other" last
const genreOrder = Object.keys(genres).sort((a, b) => {
if (a === 'Other') return 1;
if (b === 'Other') return -1;
return a.localeCompare(b);
});
genreOrder.forEach(genre => {
const group = document.createElement('optgroup');
group.label = genre;
// Shuffle within each genre group
const genreTracks = genres[genre];
for (let i = genreTracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[genreTracks[i], genreTracks[j]] = [genreTracks[j], genreTracks[i]];
}
genreTracks.forEach(track => {
const option = document.createElement('option');
option.value = track.file;
option.textContent = track.name;
group.appendChild(option);
});
select.appendChild(group);
});
// Restore previous selection if it still exists
@@ -629,6 +740,100 @@ async function setMusicVolume(e) {
}
async function loadAds() {
try {
const res = await fetch('/api/ads');
const data = await res.json();
const ads = data.ads || [];
const select = document.getElementById('ad-select');
if (!select) return;
const previousValue = select.value;
select.innerHTML = '';
ads.forEach(ad => {
const option = document.createElement('option');
option.value = ad.file;
option.textContent = ad.name;
select.appendChild(option);
});
if (previousValue && [...select.options].some(o => o.value === previousValue)) {
select.value = previousValue;
}
console.log('Loaded', ads.length, 'ads');
} catch (err) {
console.error('loadAds error:', err);
}
}
async function playAd() {
await loadAds();
const select = document.getElementById('ad-select');
const track = select?.value;
if (!track) return;
await fetch('/api/ads/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ track, action: 'play' })
});
}
async function stopAd() {
await fetch('/api/ads/stop', { method: 'POST' });
}
async function loadIdents() {
try {
const res = await fetch('/api/idents');
const data = await res.json();
const idents = data.idents || [];
const select = document.getElementById('ident-select');
if (!select) return;
const previousValue = select.value;
select.innerHTML = '';
idents.forEach(ident => {
const option = document.createElement('option');
option.value = ident.file;
option.textContent = ident.name;
select.appendChild(option);
});
if (previousValue && [...select.options].some(o => o.value === previousValue)) {
select.value = previousValue;
}
console.log('Loaded', idents.length, 'idents');
} catch (err) {
console.error('loadIdents error:', err);
}
}
async function playIdent() {
await loadIdents();
const select = document.getElementById('ident-select');
const track = select?.value;
if (!track) return;
await fetch('/api/idents/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ track, action: 'play' })
});
}
async function stopIdent() {
await fetch('/api/idents/stop', { method: 'POST' });
}
// --- Sound Effects (Server-Side) ---
async function loadSounds() {
try {
@@ -930,10 +1135,21 @@ function renderQueue(queue) {
const mins = Math.floor(caller.wait_time / 60);
const secs = caller.wait_time % 60;
const waitStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
const displayName = caller.caller_name || caller.phone;
const screenBadge = caller.screening_status === 'complete'
? '<span class="screening-badge screened">Screened</span>'
: caller.screening_status === 'screening'
? '<span class="screening-badge screening">Screening...</span>'
: '';
const summary = caller.screening_summary
? `<div class="screening-summary">${caller.screening_summary}</div>`
: '';
return `
<div class="queue-item">
<span class="queue-name">${caller.phone}</span>
<span class="queue-name">${displayName}</span>
${screenBadge}
<span class="queue-wait">waiting ${waitStr}</span>
${summary}
<button class="queue-take-btn" onclick="takeCall('${caller.caller_id}')">Take Call</button>
<button class="queue-drop-btn" onclick="dropCall('${caller.caller_id}')">Drop</button>
</div>
@@ -1089,6 +1305,23 @@ async function fetchConversationUpdates() {
}
async function exportSession() {
try {
const res = await safeFetch('/api/session/export');
const blob = new Blob([JSON.stringify(res, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `session-${res.session_id}.json`;
a.click();
URL.revokeObjectURL(url);
log(`Exported session: ${res.call_count} calls`);
} catch (err) {
log('Export error: ' + err.message);
}
}
async function stopServer() {
if (!confirm('Stop the server? You will need to restart it manually.')) return;
@@ -1100,3 +1333,182 @@ async function stopServer() {
log('Failed to stop server: ' + err.message);
}
}
// --- Voicemail ---
let _currentVmAudio = null;
async function loadVoicemails() {
try {
const res = await fetch('/api/voicemails');
const data = await res.json();
renderVoicemails(data);
} catch (err) {}
}
function renderVoicemails(voicemails) {
const list = document.getElementById('voicemail-list');
const badge = document.getElementById('voicemail-badge');
if (!list) return;
const unlistened = voicemails.filter(v => !v.listened).length;
if (badge) {
badge.textContent = unlistened;
badge.classList.toggle('hidden', unlistened === 0);
}
if (voicemails.length === 0) {
list.innerHTML = '<div class="queue-empty">No voicemails</div>';
return;
}
list.innerHTML = voicemails.map(v => {
const date = new Date(v.timestamp * 1000);
const timeStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const mins = Math.floor(v.duration / 60);
const secs = v.duration % 60;
const durStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
const unlistenedCls = v.listened ? '' : ' vm-unlistened';
return `<div class="vm-item${unlistenedCls}" data-id="${v.id}">
<div class="vm-info">
<span class="vm-phone">${v.phone}</span>
<span class="vm-time">${timeStr}</span>
<span class="vm-dur">${durStr}</span>
</div>
<div class="vm-actions">
<button class="vm-btn listen" onclick="listenVoicemail('${v.id}')">Listen</button>
<button class="vm-btn on-air" onclick="playVoicemailOnAir('${v.id}')">On Air</button>
<button class="vm-btn save" onclick="saveVoicemail('${v.id}')">Save</button>
<button class="vm-btn delete" onclick="deleteVoicemail('${v.id}')">Del</button>
</div>
</div>`;
}).join('');
}
function listenVoicemail(id) {
if (_currentVmAudio) {
_currentVmAudio.pause();
_currentVmAudio = null;
}
_currentVmAudio = new Audio(`/api/voicemail/${id}/audio`);
_currentVmAudio.play();
fetch(`/api/voicemail/${id}/mark-listened`, { method: 'POST' }).then(() => loadVoicemails());
}
async function playVoicemailOnAir(id) {
try {
await safeFetch(`/api/voicemail/${id}/play-on-air`, { method: 'POST' });
log('Playing voicemail on air');
loadVoicemails();
} catch (err) {
log('Failed to play voicemail: ' + err.message);
}
}
async function saveVoicemail(id) {
try {
await safeFetch(`/api/voicemail/${id}/save`, { method: 'POST' });
log('Voicemail saved to archive');
} catch (err) {
log('Failed to save voicemail: ' + err.message);
}
}
async function deleteVoicemail(id) {
if (!confirm('Delete this voicemail?')) return;
try {
await safeFetch(`/api/voicemail/${id}`, { method: 'DELETE' });
loadVoicemails();
} catch (err) {
log('Failed to delete voicemail: ' + err.message);
}
}
// --- Listener Emails ---
async function loadEmails() {
try {
const res = await fetch('/api/emails');
const data = await res.json();
renderEmails(data);
} catch (err) {}
}
function renderEmails(emails) {
const list = document.getElementById('email-list');
const badge = document.getElementById('email-badge');
if (!list) return;
const unread = emails.filter(e => !e.read_on_air).length;
if (badge) {
badge.textContent = unread;
badge.classList.toggle('hidden', unread === 0);
}
if (emails.length === 0) {
list.innerHTML = '<div class="queue-empty">No emails</div>';
return;
}
list.innerHTML = emails.map(e => {
const date = new Date(e.timestamp * 1000);
const timeStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const preview = e.body.length > 120 ? e.body.substring(0, 120) + '…' : e.body;
const unreadCls = e.read_on_air ? '' : ' vm-unlistened';
const senderName = e.sender.replace(/<.*>/, '').trim() || e.sender;
return `<div class="email-item${unreadCls}" data-id="${e.id}">
<div class="email-header">
<span class="email-sender">${escapeHtml(senderName)}</span>
<span class="vm-time">${timeStr}</span>
</div>
<div class="email-subject">${escapeHtml(e.subject)}</div>
<div class="email-preview">${escapeHtml(preview)}</div>
<div class="vm-actions">
<button class="vm-btn listen" onclick="viewEmail('${e.id}')">View</button>
<button class="vm-btn on-air" onclick="playEmailOnAir('${e.id}')">On Air (TTS)</button>
<button class="vm-btn delete" onclick="deleteEmail('${e.id}')">Del</button>
</div>
</div>`;
}).join('');
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function viewEmail(id) {
const item = document.querySelector(`.email-item[data-id="${id}"]`);
if (!item) return;
const existing = item.querySelector('.email-body-expanded');
if (existing) { existing.remove(); return; }
fetch('/api/emails').then(r => r.json()).then(emails => {
const em = emails.find(e => e.id === id);
if (!em) return;
const div = document.createElement('div');
div.className = 'email-body-expanded';
div.textContent = em.body;
item.appendChild(div);
});
}
async function playEmailOnAir(id) {
try {
await safeFetch(`/api/email/${id}/play-on-air`, { method: 'POST' });
log('Reading email on air (TTS)');
loadEmails();
} catch (err) {
log('Failed to play email: ' + err.message);
}
}
async function deleteEmail(id) {
if (!confirm('Delete this email?')) return;
try {
await safeFetch(`/api/email/${id}`, { method: 'DELETE' });
loadEmails();
} catch (err) {
log('Failed to delete email: ' + err.message);
}
}
Executable
+1623
View File
File diff suppressed because it is too large Load Diff
+271
View File
@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""Generate social media announcement images for Luke at the Roost.
Usage:
python make_social_post.py # regenerate with defaults
python make_social_post.py --title "NEW FEATURE" # custom title
python make_social_post.py --body body_text.txt # body from file
Outputs square (1080x1080) and landscape (1200x675) PNGs to social_posts/.
"""
import argparse
import os
import textwrap
from PIL import Image, ImageDraw, ImageFont, ImageOps
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
COVER = os.path.join(SCRIPT_DIR, "website/images/cover.png")
OUT_DIR = os.path.join(SCRIPT_DIR, "social_posts")
# Brand colors
BG = (18, 13, 7)
ACCENT = (232, 121, 29)
WHITE = (255, 255, 255)
MUTED = (175, 165, 150)
LIGHTER = (220, 215, 205)
# macOS system fonts — swap these on Linux/Windows
FONT_BLACK = "/System/Library/Fonts/Supplemental/Arial Black.ttf"
FONT_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
FONT_REG = "/System/Library/Fonts/Supplemental/Arial.ttf"
def load_font(path, size):
return ImageFont.truetype(path, size)
def text_bbox(draw, text, font):
bb = draw.textbbox((0, 0), text, font=font)
return bb[2] - bb[0], bb[3] - bb[1], bb[1] # width, height, y_offset
def wrap_text(draw, text, x, y, max_w, font, fill, line_gap=10,
cover_right=None, cover_bottom=None):
"""Word-wrap text onto the image, narrowing lines that overlap the cover.
line_gap: fixed pixel gap between lines (not a multiplier).
Returns y just below the last line of text (no trailing gap)."""
words = text.split()
lines = []
cur = ""
cur_y = y
for word in words:
test = f"{cur} {word}".strip()
eff_w = max_w
if cover_right and cover_bottom and cur_y < cover_bottom:
eff_w = cover_right - x - 20
tw, th, _ = text_bbox(draw, test, font)
if tw > eff_w and cur:
lines.append((cur, cur_y))
_, lh, _ = text_bbox(draw, cur, font)
cur_y += lh + line_gap
cur = word
else:
cur = test
if cur:
lines.append((cur, cur_y))
_, lh, _ = text_bbox(draw, cur, font)
for line, ly in lines:
draw.text((x, ly), line, font=font, fill=fill)
return cur_y + lh # return y just past the last line's bottom
def center_text(draw, text, y, canvas_w, font, fill):
tw, th, _ = text_bbox(draw, text, font)
draw.text(((canvas_w - tw) // 2, y), text, font=font, fill=fill)
return y + th
def draw_email_box(draw, email, y, canvas_w, font):
tw, th, y_off = text_bbox(draw, email, font)
px, py = 22, 16
box_w = tw + px * 2
box_x = (canvas_w - box_w) // 2
draw.rounded_rectangle(
[box_x, y, box_x + box_w, y + th + py * 2],
radius=8, fill=(45, 30, 12), outline=ACCENT, width=2,
)
draw.text((box_x + px, y + py - y_off), email, font=font, fill=ACCENT)
return y + th + py * 2
def draw_accent_bars(draw, w, h, thickness):
draw.rectangle([0, 0, w, thickness], fill=ACCENT)
draw.rectangle([0, h - thickness, w, h], fill=ACCENT)
def paste_cover(img, x, y, size, radius):
cover = Image.open(COVER).resize((size, size), Image.LANCZOS)
mask = Image.new("L", (size, size), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, size, size], radius=radius, fill=255)
img.paste(cover, (x, y), mask)
def make_square(title, paragraphs, email, filename="email_announcement_square.png"):
W = 1080
img = Image.new("RGB", (W, W), BG)
draw = ImageDraw.Draw(img)
draw_accent_bars(draw, W, W, 8)
# Cover image — top right
cover_size, cover_x, cover_y = 240, W - 290, 35
paste_cover(img, cover_x, cover_y, cover_size, 20)
cover_bottom = cover_y + cover_size + 15
m = 60
y = 40
tw_full = W - m * 2
# Header
draw.text((m, y), "LUKE AT THE ROOST", font=load_font(FONT_BOLD, 24), fill=ACCENT)
y += 30
tag = load_font(FONT_REG, 20)
draw.text((m, y), "Late-night call-in radio", font=tag, fill=MUTED)
draw.text((m, y + 26), "powered by AI", font=tag, fill=MUTED)
y += 75
# Consistent spacing constants
LINE_GAP = 12 # between lines within a block
SECTION_GAP = 32 # between sections (body→CTA, CTA→footer)
PARA_GAP = 26 # between body paragraphs
TITLE_GAP = 48 # between title and first body paragraph
# Title
y = wrap_text(draw, title, m, y, tw_full, load_font(FONT_BLACK, 72), WHITE,
line_gap=LINE_GAP, cover_right=cover_x, cover_bottom=cover_bottom)
y += TITLE_GAP
# Body paragraphs
body_font = load_font(FONT_REG, 32)
colors = [LIGHTER] + [MUTED] * (len(paragraphs) - 1)
for i, (para, color) in enumerate(zip(paragraphs, colors)):
cr = cover_x if y < cover_bottom else None
cb = cover_bottom if y < cover_bottom else None
y = wrap_text(draw, para, m, y, tw_full, body_font, color,
line_gap=LINE_GAP, cover_right=cr, cover_bottom=cb)
if i < len(paragraphs) - 1:
y += PARA_GAP
y += SECTION_GAP
# Email CTA
y = draw_email_box(draw, email, y, W, load_font(FONT_BOLD, 36))
y += SECTION_GAP
# Footer
y = center_text(draw, "New episodes drop daily. Be part of the next one.",
y, W, load_font(FONT_REG, 24), MUTED)
y += PARA_GAP
info = load_font(FONT_REG, 22)
center_text(draw, "lukeattheroost.com", y, W, info, ACCENT)
y += PARA_GAP
center_text(draw, "Spotify \u00b7 Apple Podcasts \u00b7 YouTube \u00b7 RSS",
y, W, info, MUTED)
os.makedirs(OUT_DIR, exist_ok=True)
img.save(os.path.join(OUT_DIR, filename), quality=95)
print(f"Square: {filename}")
def make_landscape(title, paragraphs, email, filename="email_announcement_twitter.png"):
TW, TH = 1200, 675
img = Image.new("RGB", (TW, TH), BG)
draw = ImageDraw.Draw(img)
draw_accent_bars(draw, TW, TH, 6)
# Cover image — top right
cover_size, cover_x, cover_y = 180, TW - 220, 22
paste_cover(img, cover_x, cover_y, cover_size, 16)
cover_bottom = cover_y + cover_size + 10
m = 45
y = 25
tw_full = TW - m * 2
# Header
draw.text((m, y), "LUKE AT THE ROOST", font=load_font(FONT_BOLD, 20), fill=ACCENT)
y += 24
draw.text((m, y), "Late-night call-in radio powered by AI",
font=load_font(FONT_REG, 17), fill=MUTED)
y += 38
# Consistent spacing constants
LINE_GAP = 8 # between lines within a block
SECTION_GAP = 20 # between sections
PARA_GAP = 16 # between body paragraphs
TITLE_GAP = 32 # between title and first body paragraph
# Title
y = wrap_text(draw, title, m, y, tw_full, load_font(FONT_BLACK, 50), WHITE,
line_gap=LINE_GAP, cover_right=cover_x, cover_bottom=cover_bottom)
y += TITLE_GAP
# Body paragraphs
body_font = load_font(FONT_REG, 23)
colors = [LIGHTER] + [MUTED] * (len(paragraphs) - 1)
for i, (para, color) in enumerate(zip(paragraphs, colors)):
cr = cover_x if y < cover_bottom else None
cb = cover_bottom if y < cover_bottom else None
y = wrap_text(draw, para, m, y, tw_full, body_font, color,
line_gap=LINE_GAP, cover_right=cr, cover_bottom=cb)
if i < len(paragraphs) - 1:
y += PARA_GAP
y += SECTION_GAP
# Email CTA
y = draw_email_box(draw, email, y, TW, load_font(FONT_BOLD, 26))
y += SECTION_GAP
# Footer
y = center_text(draw, "New episodes drop daily. Be part of the next one.",
y, TW, load_font(FONT_REG, 19), MUTED)
y += PARA_GAP
center_text(draw, "lukeattheroost.com \u00b7 Spotify \u00b7 Apple Podcasts \u00b7 YouTube",
y, TW, load_font(FONT_REG, 17), (140, 132, 120))
os.makedirs(OUT_DIR, exist_ok=True)
img.save(os.path.join(OUT_DIR, filename), quality=95)
print(f"Landscape: {filename}")
# --- Default content ---
DEFAULT_TITLE = "NOW ACCEPTING LISTENER EMAILS"
DEFAULT_EMAIL = "submissions@lukeattheroost.com"
DEFAULT_PARAGRAPHS = [
"Got a story? A question? A hot take that\u2019s been eating at you since midnight? A confession you need to get off your chest? Send it to the show.",
"The best listener emails get read live on air during the next episode \u2014 either by Luke himself on the mic, or by one of his robot friends. Your words, on the show, heard by everyone tuning in.",
"Can\u2019t call 208-439-LUKE at 2 AM? Don\u2019t want to talk on the phone? Now you\u2019ve got another way to be part of the conversation. Write in anytime \u2014 day or night, long or short, serious or unhinged.",
]
def main():
parser = argparse.ArgumentParser(description="Generate social media images")
parser.add_argument("--title", default=DEFAULT_TITLE)
parser.add_argument("--email", default=DEFAULT_EMAIL)
parser.add_argument("--body", help="Text file with paragraphs (blank-line separated)")
parser.add_argument("--prefix", default="email_announcement",
help="Output filename prefix")
args = parser.parse_args()
if args.body:
with open(args.body) as f:
paragraphs = [p.strip() for p in f.read().split("\n\n") if p.strip()]
else:
paragraphs = DEFAULT_PARAGRAPHS
make_square(args.title, paragraphs, args.email,
filename=f"{args.prefix}_square.png")
make_landscape(args.title, paragraphs, args.email,
filename=f"{args.prefix}_twitter.png")
if __name__ == "__main__":
main()
+439
View File
@@ -0,0 +1,439 @@
#!/usr/bin/env python3
"""
Podcast Stats — Aggregate reviews, comments, likes, and analytics from all platforms.
Usage:
python podcast_stats.py # All platforms
python podcast_stats.py --youtube # YouTube only
python podcast_stats.py --apple # Apple Podcasts only
python podcast_stats.py --spotify # Spotify only
python podcast_stats.py --castopod # Castopod downloads only
python podcast_stats.py --comments # Include full YouTube comments
python podcast_stats.py --json # Output as JSON
python podcast_stats.py --json --upload # Output JSON and upload to BunnyCDN
"""
import argparse
import json
import os
import re
import subprocess
import sys
from datetime import datetime, timezone
import requests
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
YOUTUBE_PLAYLIST = "PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-"
APPLE_PODCAST_ID = "1875205848"
APPLE_STOREFRONTS = ["us", "gb", "ca", "au"]
SPOTIFY_SHOW_ID = "0ZrpMigG1fo0CCN7F4YmuF"
NAS_SSH = "luke@mmgnas-10g"
NAS_SSH_PORT = "8001"
DOCKER_BIN = "/share/CACHEDEV1_DATA/.qpkg/container-station/bin/docker"
CASTOPOD_DB_CONTAINER = "castopod-mariadb-1"
BUNNY_STORAGE_ZONE = "lukeattheroost"
BUNNY_STORAGE_KEY = os.getenv("BUNNY_STORAGE_KEY", "")
BUNNY_STORAGE_REGION = "la"
BUNNY_ACCOUNT_KEY = os.getenv("BUNNY_ACCOUNT_KEY", "")
def _find_ytdlp():
"""Find yt-dlp: check local venv first, then fall back to PATH."""
import shutil
venv_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv", "bin", "yt-dlp")
if os.path.exists(venv_path):
return venv_path
path_bin = shutil.which("yt-dlp")
if path_bin:
return path_bin
return "yt-dlp"
def gather_apple_reviews():
all_reviews = []
seen_ids = set()
for storefront in APPLE_STOREFRONTS:
url = f"https://itunes.apple.com/{storefront}/rss/customerreviews/id={APPLE_PODCAST_ID}/sortby=mostrecent/json"
try:
resp = requests.get(url, timeout=15)
if resp.status_code != 200:
continue
data = resp.json()
except Exception:
continue
feed = data.get("feed", {})
entries = feed.get("entry", [])
if not entries:
continue
for entry in entries:
if "im:name" in entry and "im:rating" not in entry:
continue
review_id = entry.get("id", {}).get("label", "")
if review_id in seen_ids:
continue
seen_ids.add(review_id)
author = entry.get("author", {}).get("name", {}).get("label", "Unknown")
title = entry.get("title", {}).get("label", "")
content = entry.get("content", {}).get("label", "")
rating = int(entry.get("im:rating", {}).get("label", "0"))
updated = entry.get("updated", {}).get("label", "")
date_str = updated[:10] if updated else ""
all_reviews.append({
"author": author,
"title": title,
"content": content,
"rating": rating,
"date": date_str,
"storefront": storefront.upper(),
})
avg_rating = round(sum(r["rating"] for r in all_reviews) / len(all_reviews), 1) if all_reviews else None
return {
"avg_rating": avg_rating,
"review_count": len(all_reviews),
"reviews": all_reviews[:10],
}
def gather_spotify():
result = {"show_title": None, "rating": None, "url": f"https://open.spotify.com/show/{SPOTIFY_SHOW_ID}"}
try:
oembed_url = f"https://open.spotify.com/oembed?url=https://open.spotify.com/show/{SPOTIFY_SHOW_ID}"
resp = requests.get(oembed_url, timeout=15)
if resp.status_code == 200:
data = resp.json()
result["show_title"] = data.get("title")
show_url = f"https://open.spotify.com/show/{SPOTIFY_SHOW_ID}"
resp = requests.get(show_url, timeout=15, headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
})
rating_match = re.search(r'"ratingValue"\s*:\s*"?([\d.]+)"?', resp.text)
if rating_match:
result["rating"] = float(rating_match.group(1))
else:
rating_match2 = re.search(r'rating["\s:]*(\d+\.?\d*)\s*/\s*5', resp.text, re.IGNORECASE)
if rating_match2:
result["rating"] = float(rating_match2.group(1))
except Exception:
pass
return result
def gather_youtube(include_comments=False):
result = {
"total_views": 0,
"total_likes": 0,
"total_comments": 0,
"subscribers": None,
"videos": [],
}
try:
proc = subprocess.run(
[_find_ytdlp(), "--dump-json", "--flat-playlist",
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
capture_output=True, text=True, timeout=60
)
if proc.returncode != 0:
return result
except (FileNotFoundError, subprocess.TimeoutExpired):
return result
video_ids = []
for line in proc.stdout.strip().split("\n"):
if not line:
continue
try:
entry = json.loads(line)
vid = entry.get("id") or entry.get("url", "").split("=")[-1]
if vid:
video_ids.append(vid)
except json.JSONDecodeError:
continue
if not video_ids:
return result
total_views = 0
total_likes = 0
total_comments = 0
videos = []
for vid in video_ids:
try:
cmd = [_find_ytdlp(), "--dump-json", "--no-download", f"https://www.youtube.com/watch?v={vid}"]
if include_comments:
cmd.insert(2, "--write-comments")
vr = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
if vr.returncode != 0:
continue
vdata = json.loads(vr.stdout)
title = vdata.get("title", "Unknown")
views = vdata.get("view_count", 0) or 0
likes = vdata.get("like_count", 0) or 0
comment_count = vdata.get("comment_count", 0) or 0
upload_date = vdata.get("upload_date", "")
if upload_date:
upload_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:]}"
comments_list = []
if include_comments:
for c in (vdata.get("comments") or [])[:5]:
comments_list.append({
"author": c.get("author", "Unknown"),
"text": c.get("text", "")[:200],
"time": c.get("time_text", ""),
"likes": c.get("like_count", 0),
})
total_views += views
total_likes += likes
total_comments += comment_count
videos.append({
"title": title,
"views": views,
"likes": likes,
"comments": comment_count,
"date": upload_date,
})
except (subprocess.TimeoutExpired, json.JSONDecodeError):
continue
# Get subscriber count
if videos:
try:
vr = subprocess.run(
[_find_ytdlp(), "--dump-json", "--no-download", "--playlist-items", "1",
f"https://www.youtube.com/playlist?list={YOUTUBE_PLAYLIST}"],
capture_output=True, text=True, timeout=30
)
if vr.returncode == 0:
ch_data = json.loads(vr.stdout)
sub = ch_data.get("channel_follower_count")
if sub is not None:
result["subscribers"] = sub
except Exception:
pass
result["total_views"] = total_views
result["total_likes"] = total_likes
result["total_comments"] = total_comments
result["videos"] = videos
return result
def _run_db_query(sql):
# If running on NAS (docker socket available), exec directly
docker_bin = None
for path in [DOCKER_BIN, "/usr/bin/docker", "/usr/local/bin/docker"]:
if os.path.exists(path):
docker_bin = path
break
db_pass = os.getenv("CASTOPOD_DB_PASS", "")
if docker_bin:
cmd = [docker_bin, "exec", "-i", CASTOPOD_DB_CONTAINER,
"mysql", "-u", "castopod", f"-p{db_pass}", "castopod", "-N"]
else:
cmd = [
"ssh", "-p", NAS_SSH_PORT, NAS_SSH,
f"{DOCKER_BIN} exec -i {CASTOPOD_DB_CONTAINER} mysql -u castopod -p{db_pass} castopod -N"
]
try:
proc = subprocess.run(cmd, input=sql, capture_output=True, text=True, timeout=30)
stderr = proc.stderr.strip()
stdout = proc.stdout.strip()
if proc.returncode != 0 and not stdout:
return None, stderr
return stdout, None
except subprocess.TimeoutExpired:
return None, "timeout"
except Exception as e:
return None, str(e)
def gather_castopod():
result = {"total_downloads": 0, "unique_listeners": 0, "episodes": []}
query = (
"SELECT p.title, "
"(SELECT SUM(hits) FROM cp_analytics_podcasts WHERE podcast_id = p.id), "
"(SELECT SUM(unique_listeners) FROM cp_analytics_podcasts WHERE podcast_id = p.id) "
"FROM cp_podcasts p WHERE p.handle = 'LukeAtTheRoost' LIMIT 1;"
)
episode_query = (
"SELECT e.title, e.slug, COALESCE(SUM(ae.hits), 0), e.published_at "
"FROM cp_episodes e LEFT JOIN cp_analytics_podcasts_by_episode ae ON ae.episode_id = e.id "
"WHERE e.podcast_id = (SELECT id FROM cp_podcasts WHERE handle = 'LukeAtTheRoost') "
"GROUP BY e.id ORDER BY e.published_at DESC;"
)
out, err = _run_db_query(query)
if err or not out:
return result
parts = out.split("\t")
if len(parts) >= 3:
result["total_downloads"] = int(parts[1]) if parts[1] and parts[1] != "NULL" else 0
result["unique_listeners"] = int(parts[2]) if parts[2] and parts[2] != "NULL" else 0
elif len(parts) >= 2:
result["total_downloads"] = int(parts[1]) if parts[1] and parts[1] != "NULL" else 0
out, err = _run_db_query(episode_query)
if err or not out:
return result
for line in out.strip().split("\n"):
cols = line.split("\t")
if len(cols) >= 4:
result["episodes"].append({
"title": cols[0],
"downloads": int(cols[2]) if cols[2] else 0,
"date": cols[3][:10] if cols[3] else "",
})
return result
def print_apple(data):
print("\n⭐ APPLE PODCASTS")
print("" * 40)
if data["reviews"]:
print(f" Rating: {data['avg_rating']}/5 ({data['review_count']} reviews)")
print()
for r in data["reviews"]:
stars = "" * r["rating"] + "" * (5 - r["rating"])
print(f" {stars} \"{r['title']}\"{r['author']} ({r['date']}, {r['storefront']})")
if r["content"] and r["content"] != r["title"]:
content_preview = r["content"][:120]
if len(r["content"]) > 120:
content_preview += "..."
print(f" {content_preview}")
else:
print(" No reviews found")
def print_spotify(data):
print("\n🎵 SPOTIFY")
print("" * 40)
if data["show_title"]:
print(f" Show: {data['show_title']}")
if data["rating"]:
print(f" Rating: {data['rating']}/5")
else:
print(" Rating: Not publicly available (Spotify hides ratings from web)")
print(f" Link: {data['url']}")
def print_youtube(data):
print("\n📺 YOUTUBE")
print("" * 40)
sub_str = f" | Subscribers: {data['subscribers']:,}" if data["subscribers"] else ""
print(f" Total views: {data['total_views']:,} | Likes: {data['total_likes']:,} | Comments: {data['total_comments']:,}{sub_str}")
print()
for v in data["videos"]:
print(f" {v['title']}")
print(f" {v['views']:,} views, {v['likes']:,} likes, {v['comments']:,} comments — {v['date']}")
def print_castopod(data):
print("\n📊 DOWNLOADS (Castopod)")
print("" * 40)
print(f" Total downloads: {data['total_downloads']:,} | Unique listeners: {data['unique_listeners']:,}")
if data["episodes"]:
print()
for ep in data["episodes"]:
print(f" {ep['title']}{ep['downloads']:,} downloads ({ep['date']})")
def upload_to_bunnycdn(json_data):
storage_url = f"https://{BUNNY_STORAGE_REGION}.storage.bunnycdn.com/{BUNNY_STORAGE_ZONE}/stats.json"
resp = requests.put(
storage_url,
data=json_data,
headers={
"AccessKey": BUNNY_STORAGE_KEY,
"Content-Type": "application/json",
},
timeout=30,
)
resp.raise_for_status()
purge_url = "https://api.bunny.net/purge"
requests.post(
purge_url,
params={"url": "https://cdn.lukeattheroost.com/stats.json"},
headers={"AccessKey": BUNNY_ACCOUNT_KEY},
timeout=15,
)
print("Uploaded stats.json to BunnyCDN and purged cache", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description="Podcast analytics aggregator")
parser.add_argument("--youtube", action="store_true", help="YouTube only")
parser.add_argument("--apple", action="store_true", help="Apple Podcasts only")
parser.add_argument("--spotify", action="store_true", help="Spotify only")
parser.add_argument("--castopod", action="store_true", help="Castopod only")
parser.add_argument("--comments", action="store_true", help="Include YouTube comments")
parser.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
parser.add_argument("--upload", action="store_true", help="Upload JSON to BunnyCDN (requires --json)")
args = parser.parse_args()
if args.upload and not args.json_output:
print("Error: --upload requires --json", file=sys.stderr)
sys.exit(1)
run_all = not (args.youtube or args.apple or args.spotify or args.castopod)
results = {}
if run_all or args.castopod:
results["castopod"] = gather_castopod()
if run_all or args.apple:
results["apple"] = gather_apple_reviews()
if run_all or args.spotify:
results["spotify"] = gather_spotify()
if run_all or args.youtube:
results["youtube"] = gather_youtube(include_comments=args.comments or args.youtube)
if args.json_output:
output = {
"updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
**results,
}
json_str = json.dumps(output, indent=2, ensure_ascii=False)
print(json_str)
if args.upload:
upload_to_bunnycdn(json_str)
else:
print("=" * 45)
print(" PODCAST STATS: Luke at the Roost")
print("=" * 45)
if "castopod" in results:
print_castopod(results["castopod"])
if "apple" in results:
print_apple(results["apple"])
if "spotify" in results:
print_spotify(results["spotify"])
if "youtube" in results:
print_youtube(results["youtube"])
print()
if __name__ == "__main__":
main()
+920
View File
@@ -0,0 +1,920 @@
#!/usr/bin/env python3
"""Post-production pipeline for AI podcast stems.
Usage: python postprod.py recordings/2026-02-07_213000/ -o episode.mp3
Processes 6 aligned WAV stems (host, caller, music, sfx, ads, idents) into a
broadcast-ready MP3 with gap removal, voice compression, music ducking,
and loudness normalization.
"""
import argparse
import subprocess
import sys
import tempfile
from pathlib import Path
import numpy as np
import soundfile as sf
STEM_NAMES = ["host", "caller", "music", "sfx", "ads", "idents"]
def load_stems(stems_dir: Path) -> tuple[dict[str, np.ndarray], int]:
stems = {}
sample_rate = None
for name in STEM_NAMES:
path = stems_dir / f"{name}.wav"
if not path.exists():
print(f" {name}.wav not found, creating empty stem")
stems[name] = None
continue
data, sr = sf.read(str(path), dtype="float32")
if sample_rate is None:
sample_rate = sr
elif sr != sample_rate:
print(f" WARNING: {name}.wav has sample rate {sr}, expected {sample_rate}")
stems[name] = data
print(f" {name}: {len(data)} samples ({len(data)/sr:.1f}s)")
if sample_rate is None:
print("ERROR: No valid stems found")
sys.exit(1)
# Pad all stems to same length
max_len = max(len(s) for s in stems.values() if s is not None)
for name in STEM_NAMES:
if stems[name] is None:
stems[name] = np.zeros(max_len, dtype=np.float32)
elif len(stems[name]) < max_len:
stems[name] = np.pad(stems[name], (0, max_len - len(stems[name])))
return stems, sample_rate
def compute_rms(audio: np.ndarray, window_samples: int) -> np.ndarray:
n_windows = len(audio) // window_samples
if n_windows == 0:
return np.array([0.0])
trimmed = audio[:n_windows * window_samples].reshape(n_windows, window_samples)
return np.sqrt(np.mean(trimmed ** 2, axis=1))
def remove_gaps(stems: dict[str, np.ndarray], sr: int,
threshold_s: float = 2.0, max_gap_s: float = 15.0,
crossfade_ms: float = 30, pad_s: float = 0.5) -> dict[str, np.ndarray]:
window_ms = 50
window_samples = int(sr * window_ms / 1000)
crossfade_samples = int(sr * crossfade_ms / 1000)
# Detect gaps in everything except music (which always plays).
# This catches TTS latency gaps while protecting ad breaks and SFX transitions.
content = stems["host"] + stems["caller"] + stems["sfx"] + stems["ads"] + stems["idents"]
rms = compute_rms(content, window_samples)
# Threshold: percentile-based to sit above the mic noise floor
nonzero_rms = rms[rms > 0]
if len(nonzero_rms) == 0:
print(" No audio detected")
return stems
noise_floor = np.percentile(nonzero_rms, 20)
silence_thresh = noise_floor * 3
is_silent = rms < silence_thresh
min_silent_windows = int(threshold_s / (window_ms / 1000))
max_silent_windows = int(max_gap_s / (window_ms / 1000))
# Only cut gaps between threshold-8s — targets TTS latency, not long breaks
cuts = []
i = 0
while i < len(is_silent):
if is_silent[i]:
start = i
while i < len(is_silent) and is_silent[i]:
i += 1
length = i - start
if min_silent_windows <= length <= max_silent_windows:
# Leave pad_s of silence so the edit sounds natural
pad_samples = int(pad_s * sr)
cut_start = (start + 1) * window_samples + pad_samples
cut_end = (i - 1) * window_samples - pad_samples
if cut_end > cut_start + crossfade_samples * 2:
cuts.append((cut_start, cut_end))
else:
i += 1
if not cuts:
print(" No gaps to remove")
return stems
total_cut = sum(end - start for start, end in cuts) / sr
print(f" Removing {len(cuts)} gaps ({total_cut:.1f}s total)")
# Cut dialog/sfx/ads at gap points. Leave music uncut — just trim to fit.
result = {}
for name in STEM_NAMES:
if name == "music":
continue # handled below
audio = stems[name]
pieces = []
prev_end = 0
for cut_start, cut_end in cuts:
if prev_end < cut_start:
piece = audio[prev_end:cut_start].copy()
if pieces and len(piece) > crossfade_samples:
fade_in = np.linspace(0, 1, crossfade_samples, dtype=np.float32)
piece[:crossfade_samples] *= fade_in
if len(pieces) > 0 and len(pieces[-1]) > crossfade_samples:
fade_out = np.linspace(1, 0, crossfade_samples, dtype=np.float32)
pieces[-1][-crossfade_samples:] *= fade_out
pieces.append(piece)
prev_end = cut_end
if prev_end < len(audio):
piece = audio[prev_end:].copy()
if pieces and len(piece) > crossfade_samples:
fade_in = np.linspace(0, 1, crossfade_samples, dtype=np.float32)
piece[:crossfade_samples] *= fade_in
if len(pieces) > 0 and len(pieces[-1]) > crossfade_samples:
fade_out = np.linspace(1, 0, crossfade_samples, dtype=np.float32)
pieces[-1][-crossfade_samples:] *= fade_out
pieces.append(piece)
result[name] = np.concatenate(pieces) if pieces else np.array([], dtype=np.float32)
# Music: leave uncut, just trim to match new duration with fade-out
new_len = len(result["host"])
music = stems["music"]
if len(music) >= new_len:
music = music[:new_len].copy()
else:
music = np.pad(music, (0, new_len - len(music)))
fade_samples = int(sr * 3)
if len(music) > fade_samples:
music[-fade_samples:] *= np.linspace(1, 0, fade_samples, dtype=np.float32)
result["music"] = music
return result
def denoise(audio: np.ndarray, sr: int, tmp_dir: Path) -> np.ndarray:
"""HPF to cut rumble below 80Hz (plosives, HVAC, handling noise)."""
in_path = tmp_dir / "host_pre_denoise.wav"
out_path = tmp_dir / "host_post_denoise.wav"
sf.write(str(in_path), audio, sr)
af = "highpass=f=80:poles=2"
cmd = ["ffmpeg", "-y", "-i", str(in_path), "-af", af, str(out_path)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: denoise failed: {result.stderr[:200]}")
return audio
denoised, _ = sf.read(str(out_path), dtype="float32")
return denoised
def deess(audio: np.ndarray, sr: int, tmp_dir: Path) -> np.ndarray:
"""Reduce sibilance (harsh s/sh/ch sounds) in voice audio."""
in_path = tmp_dir / "host_pre_deess.wav"
out_path = tmp_dir / "host_post_deess.wav"
sf.write(str(in_path), audio, sr)
# Gentle high-shelf reduction at 5kHz (-4dB) to tame sibilance
# Single-pass, no phase issues unlike split-band approaches
af = "equalizer=f=5500:t=h:w=2000:g=-4"
cmd = ["ffmpeg", "-y", "-i", str(in_path), "-af", af, str(out_path)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: de-essing failed: {result.stderr[:200]}")
return audio
deessed, _ = sf.read(str(out_path), dtype="float32")
return deessed
def reduce_breaths(audio: np.ndarray, sr: int, reduction_db: float = -12) -> np.ndarray:
"""Reduce loud breaths between speech phrases."""
window_ms = 30
window_samples = int(sr * window_ms / 1000)
rms = compute_rms(audio, window_samples)
if not np.any(rms > 0):
return audio
# Speech threshold: breaths are quieter than speech but louder than silence
nonzero = rms[rms > 0]
speech_level = np.percentile(nonzero, 70)
silence_level = np.percentile(nonzero, 10)
breath_upper = speech_level * 0.3 # below 30% of speech level
breath_lower = silence_level * 2 # above 2x silence
if breath_upper <= breath_lower:
return audio
# Detect breath-length bursts (0.15-0.8s) in the breath amplitude range
min_windows = int(150 / window_ms)
max_windows = int(800 / window_ms)
breath_gain = 10 ** (reduction_db / 20)
gain_envelope = np.ones(len(rms), dtype=np.float32)
i = 0
breath_count = 0
while i < len(rms):
if breath_lower < rms[i] < breath_upper:
start = i
while i < len(rms) and breath_lower < rms[i] < breath_upper:
i += 1
length = i - start
if min_windows <= length <= max_windows:
gain_envelope[start:i] = breath_gain
breath_count += 1
else:
i += 1
if breath_count == 0:
return audio
print(f" Reduced {breath_count} breaths by {reduction_db}dB")
# Smooth transitions (10ms ramp)
ramp = max(1, int(10 / window_ms))
smoothed = gain_envelope.copy()
for i in range(1, len(smoothed)):
if smoothed[i] < smoothed[i - 1]:
smoothed[i] = smoothed[i - 1] + (smoothed[i] - smoothed[i - 1]) / ramp
elif smoothed[i] > smoothed[i - 1]:
smoothed[i] = smoothed[i - 1] + (smoothed[i] - smoothed[i - 1]) / ramp
# Expand to sample level
gain_samples = np.repeat(smoothed, window_samples)[:len(audio)]
if len(gain_samples) < len(audio):
gain_samples = np.pad(gain_samples, (0, len(audio) - len(gain_samples)), constant_values=1.0)
return (audio * gain_samples).astype(np.float32)
def limit_stem(audio: np.ndarray, sr: int, tmp_dir: Path,
stem_name: str) -> np.ndarray:
"""Hard-limit a stem to -1dB true peak to prevent clipping."""
peak = np.max(np.abs(audio))
if peak <= 0.89: # already below -1dB
return audio
in_path = tmp_dir / f"{stem_name}_pre_limit.wav"
out_path = tmp_dir / f"{stem_name}_post_limit.wav"
sf.write(str(in_path), audio, sr)
cmd = [
"ffmpeg", "-y", "-i", str(in_path),
"-af", "alimiter=limit=-1dB:level=false:attack=0.1:release=50",
str(out_path),
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: limiting failed for {stem_name}: {result.stderr[:200]}")
return audio
limited, _ = sf.read(str(out_path), dtype="float32")
peak_db = 20 * np.log10(peak)
print(f" {stem_name}: peak was {peak_db:+.1f}dB, limited to -1dB")
return limited
def compress_voice(audio: np.ndarray, sr: int, tmp_dir: Path,
stem_name: str) -> np.ndarray:
in_path = tmp_dir / f"{stem_name}_pre_comp.wav"
out_path = tmp_dir / f"{stem_name}_post_comp.wav"
sf.write(str(in_path), audio, sr)
if stem_name == "host":
# Spoken word compression: lower threshold, higher ratio, more makeup
af = "acompressor=threshold=-28dB:ratio=4:attack=5:release=600:makeup=8dB"
else:
af = "acompressor=threshold=-24dB:ratio=2.5:attack=10:release=800:makeup=6dB"
cmd = [
"ffmpeg", "-y", "-i", str(in_path),
"-af", af,
str(out_path),
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: compression failed for {stem_name}: {result.stderr[:200]}")
return audio
compressed, _ = sf.read(str(out_path), dtype="float32")
return compressed
def phone_eq(audio: np.ndarray, sr: int, tmp_dir: Path) -> np.ndarray:
"""Apply telephone EQ to make caller sound like a phone call."""
in_path = tmp_dir / "caller_pre_phone.wav"
out_path = tmp_dir / "caller_post_phone.wav"
sf.write(str(in_path), audio, sr)
# Bandpass 300-3400Hz (telephone bandwidth) + slight mid boost for presence
af = (
"highpass=f=300:poles=2,"
"lowpass=f=3400:poles=2,"
"equalizer=f=1000:t=q:w=0.8:g=4"
)
cmd = ["ffmpeg", "-y", "-i", str(in_path), "-af", af, str(out_path)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: phone EQ failed: {result.stderr[:200]}")
return audio
filtered, _ = sf.read(str(out_path), dtype="float32")
return filtered
def apply_ducking(music: np.ndarray, dialog: np.ndarray, sr: int,
duck_db: float = -20, attack_ms: float = 200,
release_ms: float = 3000,
mute_signal: np.ndarray | None = None) -> np.ndarray:
window_ms = 50
window_samples = int(sr * window_ms / 1000)
rms = compute_rms(dialog, window_samples)
# Speech detection threshold
mean_rms = np.mean(rms[rms > 0]) if np.any(rms > 0) else 1e-4
speech_thresh = mean_rms * 0.1
# Build gain envelope (per window)
duck_gain = 10 ** (duck_db / 20)
is_speech = rms > speech_thresh
target_gain = np.where(is_speech, duck_gain, 1.0).astype(np.float32)
# Mute music completely during ads with lookahead and tail
if mute_signal is not None:
mute_rms = compute_rms(mute_signal, window_samples)
mute_thresh = np.mean(mute_rms[mute_rms > 0]) * 0.1 if np.any(mute_rms > 0) else 1e-4
is_ads = mute_rms > mute_thresh
# Expand ad regions: 2s before (fade out music before ad) and 2s after (don't resume immediately)
lookahead_windows = int(2000 / window_ms)
tail_windows = int(2000 / window_ms)
expanded_ads = is_ads.copy()
for i in range(len(is_ads)):
if is_ads[i]:
start = max(0, i - lookahead_windows)
end = min(len(expanded_ads), i + tail_windows + 1)
expanded_ads[start:end] = True
target_gain[expanded_ads] = 0.0
# Smooth the envelope
attack_windows = max(1, int(attack_ms / window_ms))
release_windows = max(1, int(release_ms / window_ms))
smoothed = np.ones_like(target_gain)
for i in range(1, len(target_gain)):
if target_gain[i] < smoothed[i - 1]:
alpha = 1.0 / attack_windows
smoothed[i] = smoothed[i - 1] + alpha * (target_gain[i] - smoothed[i - 1])
else:
alpha = 1.0 / release_windows
smoothed[i] = smoothed[i - 1] + alpha * (target_gain[i] - smoothed[i - 1])
# Expand envelope to sample level
gain_samples = np.repeat(smoothed, window_samples)
if len(gain_samples) < len(music):
gain_samples = np.pad(gain_samples, (0, len(music) - len(gain_samples)), constant_values=1.0)
else:
gain_samples = gain_samples[:len(music)]
return music * gain_samples
def match_voice_levels(stems: dict[str, np.ndarray], target_rms: float = 0.1) -> dict[str, np.ndarray]:
"""Normalize host, caller, and ads stems to the same RMS level."""
for name in ["host", "caller", "ads", "idents"]:
audio = stems[name]
# Only measure non-silent portions
active = audio[np.abs(audio) > 0.001]
if len(active) == 0:
continue
current_rms = np.sqrt(np.mean(active ** 2))
if current_rms < 1e-6:
continue
gain = target_rms / current_rms
# Clamp gain to avoid extreme boosts on very quiet stems
gain = min(gain, 10.0)
stems[name] = np.clip(audio * gain, -1.0, 1.0).astype(np.float32)
db_change = 20 * np.log10(gain) if gain > 0 else 0
print(f" {name}: RMS {current_rms:.4f} -> {target_rms:.4f} ({db_change:+.1f}dB)")
return stems
def mix_stems(stems: dict[str, np.ndarray],
levels: dict[str, float] | None = None,
stereo_imaging: bool = True) -> np.ndarray:
if levels is None:
levels = {"host": 0, "caller": 0, "music": -6, "sfx": -10, "ads": 0, "idents": 0}
gains = {name: 10 ** (db / 20) for name, db in levels.items()}
max_len = max(len(s) for s in stems.values())
if stereo_imaging:
# Pan positions: -1.0 = full left, 0.0 = center, 1.0 = full right
# Using constant-power panning law
pans = {"host": 0.0, "caller": 0.15, "music": 0.0, "sfx": 0.0, "ads": 0.0, "idents": 0.0}
# Music gets stereo width via slight L/R decorrelation
music_width = 0.3
left = np.zeros(max_len, dtype=np.float64)
right = np.zeros(max_len, dtype=np.float64)
for name in STEM_NAMES:
audio = stems[name]
if len(audio) < max_len:
audio = np.pad(audio, (0, max_len - len(audio)))
signal = audio.astype(np.float64) * gains.get(name, 1.0)
if name == "music" and music_width > 0:
# Widen music: delay right channel by ~0.5ms for Haas effect
delay_samples = int(0.0005 * 44100) # ~22 samples at 44.1kHz
left += signal * (1 + music_width * 0.5)
right_delayed = np.zeros_like(signal)
right_delayed[delay_samples:] = signal[:-delay_samples] if delay_samples > 0 else signal
right += right_delayed * (1 + music_width * 0.5)
else:
pan = pans.get(name, 0.0)
# Constant-power pan: L = cos(angle), R = sin(angle)
angle = (pan + 1) * np.pi / 4 # 0 to pi/2
l_gain = np.cos(angle)
r_gain = np.sin(angle)
left += signal * l_gain
right += signal * r_gain
left = np.clip(left, -1.0, 1.0).astype(np.float32)
right = np.clip(right, -1.0, 1.0).astype(np.float32)
stereo = np.column_stack([left, right])
else:
mix = np.zeros(max_len, dtype=np.float64)
for name in STEM_NAMES:
audio = stems[name]
if len(audio) < max_len:
audio = np.pad(audio, (0, max_len - len(audio)))
mix += audio.astype(np.float64) * gains.get(name, 1.0)
mix = np.clip(mix, -1.0, 1.0).astype(np.float32)
stereo = np.column_stack([mix, mix])
return stereo
def bus_compress(audio: np.ndarray, sr: int, tmp_dir: Path) -> np.ndarray:
"""Gentle bus compression on the final stereo mix to glue everything together."""
in_path = tmp_dir / "bus_pre.wav"
out_path = tmp_dir / "bus_post.wav"
sf.write(str(in_path), audio, sr)
# Gentle glue compressor: slow attack lets transients through,
# low ratio just levels out the overall dynamics
af = "acompressor=threshold=-20dB:ratio=2:attack=20:release=300:makeup=2dB"
cmd = ["ffmpeg", "-y", "-i", str(in_path), "-af", af, str(out_path)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" WARNING: bus compression failed: {result.stderr[:200]}")
return audio
compressed, _ = sf.read(str(out_path), dtype="float32")
return compressed
def trim_silence(audio: np.ndarray, sr: int, pad_s: float = 0.5,
threshold_db: float = -50) -> np.ndarray:
"""Trim leading and trailing silence from stereo audio."""
threshold = 10 ** (threshold_db / 20)
# Use the louder channel for detection
mono = np.max(np.abs(audio), axis=1) if audio.ndim > 1 else np.abs(audio)
# Smoothed envelope for more reliable detection
window = int(sr * 0.05) # 50ms window
if len(mono) > window:
kernel = np.ones(window) / window
envelope = np.convolve(mono, kernel, mode='same')
else:
envelope = mono
above = np.where(envelope > threshold)[0]
if len(above) == 0:
return audio
pad_samples = int(pad_s * sr)
start = max(0, above[0] - pad_samples)
end = min(len(audio), above[-1] + pad_samples)
trimmed_start = start / sr
trimmed_end = (len(audio) - end) / sr
if trimmed_start > 0.1 or trimmed_end > 0.1:
print(f" Trimmed {trimmed_start:.1f}s from start, {trimmed_end:.1f}s from end")
else:
print(" No significant silence to trim")
return audio[start:end]
def apply_fades(audio: np.ndarray, sr: int,
fade_in_s: float = 1.5, fade_out_s: float = 3.0) -> np.ndarray:
"""Apply fade in/out to stereo audio using equal-power curve."""
audio = audio.copy()
# Fade in
fade_in_samples = int(fade_in_s * sr)
if fade_in_samples > 0 and fade_in_samples < len(audio):
# Equal-power: sine curve for smooth perceived volume change
curve = np.sin(np.linspace(0, np.pi / 2, fade_in_samples)).astype(np.float32)
if audio.ndim > 1:
audio[:fade_in_samples] *= curve[:, np.newaxis]
else:
audio[:fade_in_samples] *= curve
# Fade out
fade_out_samples = int(fade_out_s * sr)
if fade_out_samples > 0 and fade_out_samples < len(audio):
curve = np.sin(np.linspace(np.pi / 2, 0, fade_out_samples)).astype(np.float32)
if audio.ndim > 1:
audio[-fade_out_samples:] *= curve[:, np.newaxis]
else:
audio[-fade_out_samples:] *= curve
print(f" Fade in: {fade_in_s}s, fade out: {fade_out_s}s")
return audio
def detect_chapters(stems: dict[str, np.ndarray], sr: int) -> list[dict]:
"""Auto-detect chapter boundaries from stem activity."""
window_s = 2 # 2-second analysis windows
window_samples = int(sr * window_s)
n_windows = min(len(s) for s in stems.values()) // window_samples
if n_windows == 0:
return []
chapters = []
current_type = None
chapter_start = 0
for w in range(n_windows):
start = w * window_samples
end = start + window_samples
ads_rms = np.sqrt(np.mean(stems["ads"][start:end] ** 2))
caller_rms = np.sqrt(np.mean(stems["caller"][start:end] ** 2))
host_rms = np.sqrt(np.mean(stems["host"][start:end] ** 2))
# Classify this window
if ads_rms > 0.005:
seg_type = "Ad Break"
elif caller_rms > 0.005:
seg_type = "Caller"
elif host_rms > 0.005:
seg_type = "Host"
else:
seg_type = current_type # keep current during silence
if seg_type != current_type and seg_type is not None:
if current_type is not None:
chapters.append({
"title": current_type,
"start_ms": int(chapter_start * 1000),
"end_ms": int(w * window_s * 1000),
})
current_type = seg_type
chapter_start = w * window_s
# Final chapter
if current_type is not None:
chapters.append({
"title": current_type,
"start_ms": int(chapter_start * 1000),
"end_ms": int(n_windows * window_s * 1000),
})
# Merge consecutive chapters of same type
merged = []
for ch in chapters:
if merged and merged[-1]["title"] == ch["title"]:
merged[-1]["end_ms"] = ch["end_ms"]
else:
merged.append(ch)
# Number duplicate types (Caller 1, Caller 2, etc.)
type_counts = {}
for ch in merged:
base = ch["title"]
type_counts[base] = type_counts.get(base, 0) + 1
if type_counts[base] > 1 or base in ("Caller", "Ad Break"):
ch["title"] = f"{base} {type_counts[base]}"
# Filter out very short chapters (< 10s)
merged = [ch for ch in merged if ch["end_ms"] - ch["start_ms"] >= 10000]
return merged
def write_ffmpeg_chapters(chapters: list[dict], output_path: Path):
"""Write an ffmpeg-format metadata file with chapter markers."""
lines = [";FFMETADATA1"]
for ch in chapters:
lines.append("[CHAPTER]")
lines.append("TIMEBASE=1/1000")
lines.append(f"START={ch['start_ms']}")
lines.append(f"END={ch['end_ms']}")
lines.append(f"title={ch['title']}")
output_path.write_text("\n".join(lines) + "\n")
def normalize_and_export(audio: np.ndarray, sr: int, output_path: Path,
target_lufs: float = -16, bitrate: str = "128k",
tmp_dir: Path = None,
metadata: dict | None = None,
chapters_file: Path | None = None):
import json
import shutil
tmp_wav = tmp_dir / "pre_loudnorm.wav"
sf.write(str(tmp_wav), audio, sr)
# Pass 1: measure loudness
measure_cmd = [
"ffmpeg", "-y", "-i", str(tmp_wav),
"-af", f"loudnorm=I={target_lufs}:TP=-1:LRA=11:print_format=json",
"-f", "null", "-",
]
result = subprocess.run(measure_cmd, capture_output=True, text=True)
stderr = result.stderr
json_start = stderr.rfind("{")
json_end = stderr.rfind("}") + 1
if json_start >= 0 and json_end > json_start:
stats = json.loads(stderr[json_start:json_end])
else:
print(" WARNING: couldn't parse loudnorm stats, using defaults")
stats = {
"input_i": "-23", "input_tp": "-1", "input_lra": "11",
"input_thresh": "-34",
}
# Pass 2: normalize + limiter + export MP3
loudnorm_filter = (
f"loudnorm=I={target_lufs}:TP=-1:LRA=11"
f":measured_I={stats['input_i']}"
f":measured_TP={stats['input_tp']}"
f":measured_LRA={stats['input_lra']}"
f":measured_thresh={stats['input_thresh']}"
f":linear=true"
)
export_cmd = ["ffmpeg", "-y", "-i", str(tmp_wav)]
if chapters_file and chapters_file.exists():
export_cmd += ["-i", str(chapters_file), "-map_metadata", "1"]
export_cmd += [
"-af", f"{loudnorm_filter},alimiter=limit=-1dB:level=false",
"-ab", bitrate, "-ar", str(sr),
]
if metadata:
for key, value in metadata.items():
if value and not key.startswith("_"):
export_cmd += ["-metadata", f"{key}={value}"]
export_cmd.append(str(output_path))
result = subprocess.run(export_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" ERROR: export failed: {result.stderr[:300]}")
sys.exit(1)
# Embed artwork as a second pass (avoids complex multi-input mapping)
artwork = metadata.get("_artwork") if metadata else None
if artwork and Path(artwork).exists():
tmp_mp3 = tmp_dir / "with_art.mp3"
art_cmd = [
"ffmpeg", "-y", "-i", str(output_path), "-i", artwork,
"-map", "0:a", "-map", "1:0",
"-c:a", "copy", "-id3v2_version", "3",
"-metadata:s:v", "title=Album cover",
"-metadata:s:v", "comment=Cover (front)",
"-disposition:v", "attached_pic",
str(tmp_mp3),
]
art_result = subprocess.run(art_cmd, capture_output=True, text=True)
if art_result.returncode == 0:
shutil.move(str(tmp_mp3), str(output_path))
print(f" Embedded artwork: {artwork}")
else:
print(f" WARNING: artwork embedding failed: {art_result.stderr[:200]}")
def main():
parser = argparse.ArgumentParser(description="Post-production for AI podcast stems")
parser.add_argument("stems_dir", type=Path, help="Directory containing stem WAV files")
parser.add_argument("-o", "--output", type=str, default="episode.mp3", help="Output filename")
parser.add_argument("--gap-threshold", type=float, default=2.0, help="Min silence to cut (seconds)")
parser.add_argument("--duck-amount", type=float, default=-20, help="Music duck in dB")
parser.add_argument("--target-lufs", type=float, default=-16, help="Target loudness (LUFS)")
parser.add_argument("--bitrate", type=str, default="128k", help="MP3 bitrate")
parser.add_argument("--fade-in", type=float, default=1.5, help="Fade in duration (seconds)")
parser.add_argument("--fade-out", type=float, default=3.0, help="Fade out duration (seconds)")
# Metadata
parser.add_argument("--title", type=str, help="Episode title (ID3 tag)")
parser.add_argument("--artist", type=str, default="Luke at the Roost", help="Artist name")
parser.add_argument("--album", type=str, default="Luke at the Roost", help="Album/show name")
parser.add_argument("--episode-num", type=str, help="Episode number (track tag)")
parser.add_argument("--artwork", type=str, help="Path to artwork image (embedded in MP3)")
# Skip flags
parser.add_argument("--no-gap-removal", action="store_true", help="Skip gap removal")
parser.add_argument("--no-denoise", action="store_true", help="Skip noise reduction + HPF")
parser.add_argument("--no-deess", action="store_true", help="Skip de-essing")
parser.add_argument("--no-breath-reduction", action="store_true", help="Skip breath reduction")
parser.add_argument("--no-compression", action="store_true", help="Skip voice compression")
parser.add_argument("--no-phone-eq", action="store_true", help="Skip caller phone EQ")
parser.add_argument("--no-ducking", action="store_true", help="Skip music ducking")
parser.add_argument("--no-stereo", action="store_true", help="Skip stereo imaging (mono mix)")
parser.add_argument("--no-trim", action="store_true", help="Skip silence trimming")
parser.add_argument("--no-fade", action="store_true", help="Skip fade in/out")
parser.add_argument("--no-chapters", action="store_true", help="Skip chapter markers")
parser.add_argument("--dry-run", action="store_true", help="Show what would be done")
args = parser.parse_args()
stems_dir = args.stems_dir
if not stems_dir.exists():
print(f"ERROR: directory not found: {stems_dir}")
sys.exit(1)
# Resolve output path
output_path = Path(args.output)
if not output_path.is_absolute():
output_path = stems_dir / output_path
print(f"Post-production: {stems_dir} -> {output_path}")
if args.dry_run:
print("Dry run — exiting")
return
total_steps = 15
# Step 1: Load
print(f"\n[1/{total_steps}] Loading stems...")
stems, sr = load_stems(stems_dir)
# Step 2: Gap removal
print(f"\n[2/{total_steps}] Gap removal...")
if not args.no_gap_removal:
stems = remove_gaps(stems, sr, threshold_s=args.gap_threshold)
else:
print(" Skipped")
# Step 3: Limit ads + SFX (prevent clipping)
print(f"\n[3/{total_steps}] Limiting ads + SFX...")
with tempfile.TemporaryDirectory() as tmp:
tmp_dir = Path(tmp)
for name in ["ads", "sfx", "idents"]:
if np.any(stems[name] != 0):
stems[name] = limit_stem(stems[name], sr, tmp_dir, name)
# Step 4: Host mic noise reduction + HPF
print(f"\n[4/{total_steps}] Host noise reduction + HPF...")
if not args.no_denoise and np.any(stems["host"] != 0):
with tempfile.TemporaryDirectory() as tmp:
stems["host"] = denoise(stems["host"], sr, Path(tmp))
print(" Applied")
else:
print(" Skipped" if args.no_denoise else " No host audio")
# Step 5: De-essing
print(f"\n[5/{total_steps}] De-essing host...")
if not args.no_deess and np.any(stems["host"] != 0):
with tempfile.TemporaryDirectory() as tmp:
stems["host"] = deess(stems["host"], sr, Path(tmp))
print(" Applied")
else:
print(" Skipped" if args.no_deess else " No host audio")
# Step 6: Breath reduction
print(f"\n[6/{total_steps}] Breath reduction...")
if not args.no_breath_reduction and np.any(stems["host"] != 0):
stems["host"] = reduce_breaths(stems["host"], sr)
else:
print(" Skipped" if args.no_breath_reduction else " No host audio")
# Step 7: Voice compression
print(f"\n[7/{total_steps}] Voice compression...")
if not args.no_compression:
with tempfile.TemporaryDirectory() as tmp:
tmp_dir = Path(tmp)
for name in ["host", "caller"]:
if np.any(stems[name] != 0):
print(f" Compressing {name}...")
stems[name] = compress_voice(stems[name], sr, tmp_dir, name)
else:
print(" Skipped")
# Step 8: Phone EQ on caller
print(f"\n[8/{total_steps}] Phone EQ on caller...")
if not args.no_phone_eq and np.any(stems["caller"] != 0):
with tempfile.TemporaryDirectory() as tmp:
stems["caller"] = phone_eq(stems["caller"], sr, Path(tmp))
print(" Applied")
else:
print(" Skipped" if args.no_phone_eq else " No caller audio")
# Step 9: Match voice levels
print(f"\n[9/{total_steps}] Matching voice levels...")
stems = match_voice_levels(stems)
# Step 10: Music ducking
print(f"\n[10/{total_steps}] Music ducking...")
if not args.no_ducking:
dialog = stems["host"] + stems["caller"]
if np.any(dialog != 0) and np.any(stems["music"] != 0):
stems["music"] = apply_ducking(stems["music"], dialog, sr, duck_db=args.duck_amount,
mute_signal=stems["ads"] + stems["idents"])
print(" Applied")
else:
print(" No dialog or music to duck")
else:
print(" Skipped")
# Step 11: Stereo mix
print(f"\n[11/{total_steps}] Mixing...")
stereo = mix_stems(stems, stereo_imaging=not args.no_stereo)
imaging = "stereo" if not args.no_stereo else "mono"
print(f" Mixed to {imaging}: {len(stereo)} samples ({len(stereo)/sr:.1f}s)")
# Step 12: Bus compression
print(f"\n[12/{total_steps}] Bus compression...")
with tempfile.TemporaryDirectory() as tmp:
stereo = bus_compress(stereo, sr, Path(tmp))
print(" Applied")
# Step 13: Silence trimming
print(f"\n[13/{total_steps}] Trimming silence...")
if not args.no_trim:
stereo = trim_silence(stereo, sr)
else:
print(" Skipped")
# Step 14: Fade in/out
print(f"\n[14/{total_steps}] Fades...")
if not args.no_fade:
stereo = apply_fades(stereo, sr, fade_in_s=args.fade_in, fade_out_s=args.fade_out)
else:
print(" Skipped")
# Step 15: Normalize + export with metadata and chapters
print(f"\n[15/{total_steps}] Loudness normalization + export...")
# Build metadata dict
meta = {}
if args.title:
meta["title"] = args.title
if args.artist:
meta["artist"] = args.artist
if args.album:
meta["album"] = args.album
if args.episode_num:
meta["track"] = args.episode_num
if args.artwork:
meta["_artwork"] = args.artwork
# Auto-detect chapters
chapters = []
if not args.no_chapters:
chapters = detect_chapters(stems, sr)
if chapters:
print(f" Detected {len(chapters)} chapters:")
for ch in chapters:
start_s = ch["start_ms"] / 1000
end_s = ch["end_ms"] / 1000
print(f" {start_s:6.1f}s - {end_s:6.1f}s {ch['title']}")
else:
print(" No chapters detected")
else:
print(" Skipped")
with tempfile.TemporaryDirectory() as tmp:
tmp_dir = Path(tmp)
chapters_file = None
if chapters:
chapters_file = tmp_dir / "chapters.txt"
write_ffmpeg_chapters(chapters, chapters_file)
normalize_and_export(stereo, sr, output_path,
target_lufs=args.target_lufs,
bitrate=args.bitrate,
tmp_dir=tmp_dir,
metadata=meta if meta else None,
chapters_file=chapters_file)
print(f"\nDone! Output: {output_path}")
if __name__ == "__main__":
main()
+1189 -69
View File
File diff suppressed because it is too large Load Diff
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Re-label podcast transcripts with LUKE:/CALLER: speaker labels using LLM."""
import os, re, sys, time, requests
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("OPENROUTER_API_KEY")
TRANSCRIPT_DIR = Path(__file__).parent / "website" / "transcripts"
MODEL = "anthropic/claude-3.5-sonnet"
CHUNK_SIZE = 8000
PROMPT = """Insert speaker labels into this radio show transcript. The show is "Luke at the Roost". The host is LUKE. Callers call in one at a time.
CRITICAL: Output EVERY SINGLE WORD from the input. Do NOT summarize, shorten, paraphrase, or skip ANY text. The output must contain the EXACT SAME words as the input, with ONLY speaker labels and line breaks added.
At each speaker change, insert a blank line and the new speaker's label (e.g., "LUKE:" or "REGGIE:").
Speaker identification:
- LUKE is the host — he introduces callers, asks questions, does sponsor reads, opens and closes the show
- Callers are introduced by name by Luke (e.g., "let's talk to Earl", "next up Brenda")
- Use caller FIRST NAME in caps as the label
- When Luke says "Tell me about..." or asks a question, that's LUKE
- When someone responds with their story/opinion/answer, that's the CALLER
Output format — ONLY the labeled transcript with blank lines between turns. No notes, no commentary."""
CONTEXT_PROMPT = "\n\nCONTEXT: The previous section ended with the speaker {speaker}. Last few words: \"{tail}\""
def chunk_text(text, max_chars=CHUNK_SIZE):
if len(text) <= max_chars:
return [text]
chunks = []
while text:
if len(text) <= max_chars:
# Merge tiny tails into the previous chunk
if chunks and len(text) < 1000:
chunks[-1] = chunks[-1] + " " + text
else:
chunks.append(text)
break
# Find a good break point near max_chars
pos = text[:max_chars].rfind('. ')
if pos < max_chars // 2:
pos = text[:max_chars].rfind('? ')
if pos < max_chars // 2:
pos = text[:max_chars].rfind('! ')
if pos < max_chars // 2:
pos = max_chars
chunks.append(text[:pos + 1].strip())
text = text[pos + 1:].strip()
return chunks
def label_chunk(text, context=""):
prompt = PROMPT + "\n\nTRANSCRIPT:\n" + text
if context:
prompt += context
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"model": MODEL,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 8192,
"temperature": 0
}
)
if response.status_code != 200:
print(f" API error: {response.status_code} {response.text[:200]}")
return None
content = response.json()["choices"][0]["message"]["content"].strip()
# Remove any markdown code block wrappers
if content.startswith("```"):
content = re.sub(r'^```\w*\n?', '', content)
content = re.sub(r'\n?```$', '', content)
return content
def get_last_speaker(text):
lines = text.strip().split('\n')
for line in reversed(lines):
match = re.match(r'^([A-Z][A-Z\s\'-]+?):', line.strip())
if match:
return match.group(1)
return "LUKE"
def validate_output(original, labeled):
"""Basic validation that the output looks right."""
# Check that speaker labels exist (at least 1 for short chunks)
speaker_lines = re.findall(r'^[A-Z][A-Z\s\'-]+?:', labeled, re.MULTILINE)
if len(speaker_lines) < 1:
return False
# Check that output isn't drastically shorter (allowing for some reformatting)
orig_words = len(original.split())
labeled_words = len(labeled.split())
if labeled_words < orig_words * 0.5:
print(f" WARNING: Output is {labeled_words} words vs {orig_words} input words ({labeled_words * 100 // orig_words}%)")
return False
return True
def process_transcript(filepath):
text = filepath.read_text().strip()
# Strip existing timestamp markers
text = re.sub(r'\[[\d:]+\]\s*', '', text)
# Normalize whitespace
text = re.sub(r'\n+', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
print(f" {len(text)} chars")
chunks = chunk_text(text)
print(f" {len(chunks)} chunk(s)")
labeled_parts = []
context = ""
for i, chunk in enumerate(chunks):
print(f" Processing chunk {i + 1}/{len(chunks)} ({len(chunk)} chars)...")
labeled = label_chunk(chunk, context)
if labeled is None:
print(f" ERROR: API call failed for chunk {i + 1}")
return None
if not validate_output(chunk, labeled):
print(f" ERROR: Validation failed for chunk {i + 1}")
return None
labeled_parts.append(labeled)
# Build context for next chunk
last_speaker = get_last_speaker(labeled)
tail = labeled.strip()[-100:]
context = CONTEXT_PROMPT.format(speaker=last_speaker, tail=tail)
if i < len(chunks) - 1:
time.sleep(0.5)
# Join parts, ensuring proper spacing between chunks
result = "\n\n".join(labeled_parts)
# Normalize: ensure exactly one blank line between speaker turns
result = re.sub(r'\n{3,}', '\n\n', result)
# Fix format: put speaker label on same line as text (SPEAKER:\ntext -> SPEAKER: text)
result = re.sub(r'^([A-Z][A-Z\s\'-]+?):\s*\n(?!\n)', r'\1: ', result, flags=re.MULTILINE)
return result
def main():
if not API_KEY:
print("Error: OPENROUTER_API_KEY not set")
sys.exit(1)
files = sys.argv[1:] if len(sys.argv) > 1 else None
if files:
transcripts = [TRANSCRIPT_DIR / f for f in files]
else:
transcripts = sorted(TRANSCRIPT_DIR.glob("*.txt"))
for filepath in transcripts:
if not filepath.exists():
print(f"Skipping {filepath.name} (not found)")
continue
print(f"\nProcessing: {filepath.name}")
labeled = process_transcript(filepath)
if labeled is None:
print(f" SKIPPED (processing failed)")
continue
filepath.write_text(labeled + "\n")
print(f" Saved ({len(labeled)} chars)")
print("\nDone!")
if __name__ == "__main__":
main()
+772
View File
@@ -0,0 +1,772 @@
#!/usr/bin/env python3
"""Upload podcast clips to social media (direct YouTube & Bluesky, Postiz for others).
Usage:
python upload_clips.py # interactive: pick episode, clips, platforms
python upload_clips.py clips/episode-12/ # pick clips and platforms interactively
python upload_clips.py clips/episode-12/ --clip 1 --platforms ig,yt
python upload_clips.py clips/episode-12/ --yes # skip all prompts, upload everything
"""
import argparse
import json
import re
import sys
from pathlib import Path
import requests
from atproto import Client as BskyClient
from dotenv import load_dotenv
import os
load_dotenv(Path(__file__).parent / ".env")
POSTIZ_API_KEY = os.getenv("POSTIZ_API_KEY")
POSTIZ_URL = os.getenv("POSTIZ_URL", "https://social.lukeattheroost.com")
BSKY_HANDLE = os.getenv("BSKY_HANDLE", "lukeattheroost.bsky.social")
BSKY_APP_PASSWORD = os.getenv("BSKY_APP_PASSWORD")
YT_CLIENT_SECRETS = Path(__file__).parent / "youtube_client_secrets.json"
YT_TOKEN_FILE = Path(__file__).parent / "youtube_token.json"
PLATFORM_ALIASES = {
"ig": "instagram", "insta": "instagram", "instagram": "instagram",
"yt": "youtube", "youtube": "youtube",
"fb": "facebook", "facebook": "facebook",
"bsky": "bluesky", "bluesky": "bluesky",
"masto": "mastodon", "mastodon": "mastodon",
"nostr": "nostr",
"li": "linkedin", "linkedin": "linkedin",
"threads": "threads",
"tt": "tiktok", "tiktok": "tiktok",
}
PLATFORM_DISPLAY = {
"instagram": "Instagram Reels",
"youtube": "YouTube Shorts",
"facebook": "Facebook Reels",
"bluesky": "Bluesky",
"mastodon": "Mastodon",
"nostr": "Nostr",
"linkedin": "LinkedIn",
"threads": "Threads",
"tiktok": "TikTok",
}
ALL_PLATFORMS = list(PLATFORM_DISPLAY.keys())
UPLOAD_LEDGER_FILE = "upload-history.json"
def load_upload_history(clips_dir: Path) -> dict:
"""Load upload history for a clips directory.
Returns dict mapping clip_file -> list of platforms already uploaded to.
"""
ledger = clips_dir / UPLOAD_LEDGER_FILE
if ledger.exists():
with open(ledger) as f:
return json.load(f)
return {}
def save_upload_history(clips_dir: Path, history: dict):
with open(clips_dir / UPLOAD_LEDGER_FILE, "w") as f:
json.dump(history, f, indent=2)
def get_api_url(path: str) -> str:
base = POSTIZ_URL.rstrip("/")
return f"{base}/api/public/v1{path}"
def api_headers() -> dict:
return {
"Authorization": POSTIZ_API_KEY,
"Content-Type": "application/json",
}
def fetch_integrations() -> list[dict]:
resp = requests.get(get_api_url("/integrations"), headers=api_headers(), timeout=15)
if resp.status_code != 200:
print(f"Error fetching integrations: {resp.status_code} {resp.text[:200]}")
sys.exit(1)
return resp.json()
def find_integration(integrations: list[dict], provider: str) -> dict | None:
for integ in integrations:
if integ.get("identifier", "").startswith(provider) and not integ.get("disabled"):
return integ
return None
def upload_file(file_path: Path) -> dict:
headers = {"Authorization": POSTIZ_API_KEY}
with open(file_path, "rb") as f:
resp = requests.post(
get_api_url("/upload"),
headers=headers,
files={"file": (file_path.name, f, "video/mp4")},
timeout=120,
)
if resp.status_code not in (200, 201):
print(f"Upload failed: {resp.status_code} {resp.text[:200]}")
return {}
return resp.json()
def build_content(clip: dict, platform: str) -> str:
desc = clip.get("description", clip.get("caption_text", ""))
hashtags = clip.get("hashtags", [])
hashtag_str = " ".join(hashtags)
if platform == "bluesky":
if hashtags and len(desc) + 2 + len(hashtag_str) <= 300:
return desc + "\n\n" + hashtag_str
return desc[:300]
parts = [desc]
if hashtags:
parts.append("\n\n" + hashtag_str)
if platform in ("youtube", "facebook"):
parts.append("\n\nListen to the full episode: lukeattheroost.com")
return "".join(parts)
def build_settings(clip: dict, platform: str) -> dict:
if platform == "instagram":
return {"__type": "instagram", "post_type": "post", "collaborators": []}
if platform == "youtube":
yt_tags = [{"value": h.lstrip("#"), "label": h.lstrip("#")}
for h in clip.get("hashtags", [])]
return {
"__type": "youtube",
"title": clip["title"],
"type": "public",
"selfDeclaredMadeForKids": "no",
"thumbnail": None,
"tags": yt_tags,
}
if platform == "tiktok":
return {
"__type": "tiktok",
"privacy_level": "PUBLIC_TO_EVERYONE",
"duet": False,
"stitch": False,
"comment": True,
"autoAddMusic": "no",
"brand_content_toggle": False,
"brand_organic_toggle": False,
"content_posting_method": "DIRECT_POST",
}
return {"__type": platform}
def post_to_bluesky(clip: dict, clip_file: Path) -> bool:
"""Post a clip directly to Bluesky via atproto (bypasses Postiz)."""
import time
import httpx
from atproto import models
if not BSKY_APP_PASSWORD:
print(" Error: BSKY_APP_PASSWORD not set in .env")
return False
client = BskyClient()
client.login(BSKY_HANDLE, BSKY_APP_PASSWORD)
did = client.me.did
video_data = clip_file.read_bytes()
# Get a service auth token scoped to the user's PDS (required by video service)
from urllib.parse import urlparse
pds_host = urlparse(client._session.pds_endpoint).hostname
service_auth = client.com.atproto.server.get_service_auth(
{"aud": f"did:web:{pds_host}", "lxm": "com.atproto.repo.uploadBlob"}
)
token = service_auth.token
# Upload video to Bluesky's video processing service (not the PDS)
print(f" Uploading video ({len(video_data) / 1_000_000:.1f} MB)...")
upload_resp = httpx.post(
"https://video.bsky.app/xrpc/app.bsky.video.uploadVideo",
params={"did": did, "name": clip_file.name},
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "video/mp4",
},
content=video_data,
timeout=120,
)
if upload_resp.status_code not in (200, 409):
print(f" Upload failed: {upload_resp.status_code} {upload_resp.text[:200]}")
return False
upload_data = upload_resp.json()
job_id = upload_data.get("jobId") or upload_data.get("jobStatus", {}).get("jobId")
if not job_id:
print(f" No jobId returned: {upload_resp.text[:200]}")
return False
print(f" Video processing (job {job_id})...")
# Poll until video is processed
session_token = client._session.access_jwt
blob = None
while True:
status_resp = httpx.get(
"https://video.bsky.app/xrpc/app.bsky.video.getJobStatus",
params={"jobId": job_id},
headers={"Authorization": f"Bearer {session_token}"},
timeout=15,
)
resp_data = status_resp.json()
status = resp_data.get("jobStatus") or resp_data
state = status.get("state")
if state == "JOB_STATE_COMPLETED":
blob = status.get("blob")
break
if state == "JOB_STATE_FAILED":
err = status.get("error") or status.get("message") or "unknown"
print(f" Video processing failed: {err}")
return False
progress = status.get("progress", 0)
print(f" Processing... {progress}%")
time.sleep(3)
if not blob:
print(" No blob returned after processing")
return False
text = build_content(clip, "bluesky")
embed = models.AppBskyEmbedVideo.Main(
video=models.blob_ref.BlobRef(
mime_type=blob["mimeType"],
size=blob["size"],
ref=models.blob_ref.IpldLink(link=blob["ref"]["$link"]),
),
alt=clip.get("caption_text", clip["title"]),
aspect_ratio=models.AppBskyEmbedDefs.AspectRatio(width=1080, height=1920),
)
client.send_post(text=text, embed=embed)
return True
def get_youtube_service():
"""Authenticate with YouTube API. First run opens a browser, then reuses saved token."""
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build as yt_build
scopes = ["https://www.googleapis.com/auth/youtube.upload"]
creds = None
if YT_TOKEN_FILE.exists():
creds = Credentials.from_authorized_user_file(str(YT_TOKEN_FILE), scopes)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not YT_CLIENT_SECRETS.exists():
print(" Error: youtube_client_secrets.json not found")
print(" Download OAuth2 Desktop App credentials from Google Cloud Console")
return None
flow = InstalledAppFlow.from_client_secrets_file(str(YT_CLIENT_SECRETS), scopes)
creds = flow.run_local_server(port=8090)
with open(YT_TOKEN_FILE, "w") as f:
f.write(creds.to_json())
return yt_build("youtube", "v3", credentials=creds)
def post_to_youtube(clip: dict, clip_file: Path) -> bool:
"""Upload a clip directly to YouTube Shorts via the Data API."""
import time
import random
from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError
youtube = get_youtube_service()
if not youtube:
return False
title = clip["title"]
if "#Shorts" not in title:
title = f"{title} #Shorts"
description = build_content(clip, "youtube")
if "#Shorts" not in description:
description += "\n\n#Shorts"
tags = [h.lstrip("#") for h in clip.get("hashtags", [])]
if "Shorts" not in tags:
tags.insert(0, "Shorts")
body = {
"snippet": {
"title": title[:100],
"description": description,
"tags": tags,
"categoryId": "24", # Entertainment
},
"status": {
"privacyStatus": "public",
"selfDeclaredMadeForKids": False,
},
}
media = MediaFileUpload(
str(clip_file),
mimetype="video/mp4",
chunksize=256 * 1024,
resumable=True,
)
request = youtube.videos().insert(part="snippet,status", body=body, media_body=media)
file_size = clip_file.stat().st_size / 1_000_000
print(f" Uploading video ({file_size:.1f} MB)...")
response = None
retry = 0
while response is None:
try:
status, response = request.next_chunk()
if status:
print(f" Upload {int(status.progress() * 100)}%...")
except HttpError as e:
if e.resp.status in (500, 502, 503, 504) and retry < 5:
retry += 1
wait = random.random() * (2 ** retry)
print(f" Retrying in {wait:.1f}s...")
time.sleep(wait)
else:
print(f" YouTube API error: {e}")
return False
video_id = response["id"]
print(f" https://youtube.com/shorts/{video_id}")
return video_id
def create_post(integration_id: str, content: str, media: dict,
settings: dict, schedule: str | None = None) -> dict:
from datetime import datetime, timezone
post_type = "schedule" if schedule else "now"
date = schedule or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
payload = {
"type": post_type,
"date": date,
"shortLink": False,
"tags": [],
"posts": [
{
"integration": {"id": integration_id},
"value": [
{
"content": content,
"image": [media] if media else [],
}
],
"settings": settings,
}
],
}
resp = requests.post(
get_api_url("/posts"),
headers=api_headers(),
json=payload,
timeout=30,
)
if resp.status_code not in (200, 201):
print(f"Post creation failed: {resp.status_code} {resp.text[:300]}")
return {}
return resp.json()
def main():
valid_names = sorted(set(PLATFORM_ALIASES.keys()))
parser = argparse.ArgumentParser(description="Upload podcast clips to social media via Postiz")
parser.add_argument("clips_dir", nargs="?", help="Path to clips directory (e.g. clips/episode-12/). If omitted, shows a picker.")
parser.add_argument("--clip", "-c", type=int, help="Upload only clip N (1-indexed)")
parser.add_argument("--platforms", "-p",
help=f"Comma-separated platforms ({','.join(ALL_PLATFORMS)}). Default: all")
parser.add_argument("--schedule", "-s", help="Schedule time (ISO 8601, e.g. 2026-02-16T10:00:00)")
parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
parser.add_argument("--dry-run", action="store_true", help="Show what would be uploaded without posting")
args = parser.parse_args()
if not POSTIZ_API_KEY:
print("Error: POSTIZ_API_KEY not set in .env")
sys.exit(1)
# Resolve clips directory — pick interactively if not provided
if args.clips_dir:
clips_dir = Path(args.clips_dir).expanduser().resolve()
else:
clips_root = Path(__file__).parent / "clips"
episode_dirs = sorted(
[d for d in clips_root.iterdir()
if d.is_dir() and not d.name.startswith(".") and (d / "clips-metadata.json").exists()],
key=lambda d: (int(m.group(1)) if (m := re.search(r'(\d+)', d.name)) else 0, d.name),
)
if not episode_dirs:
print("No clip directories found in clips/. Run make_clips.py first.")
sys.exit(1)
print("\nAvailable episodes:\n")
for i, d in enumerate(episode_dirs):
with open(d / "clips-metadata.json") as f:
meta = json.load(f)
print(f" {i+1}. {d.name} ({len(meta)} clip{'s' if len(meta) != 1 else ''})")
print()
while True:
try:
choice = input("Which episode? ").strip()
idx = int(choice) - 1
if 0 <= idx < len(episode_dirs):
clips_dir = episode_dirs[idx]
break
print(f" Enter 1-{len(episode_dirs)}")
except (ValueError, EOFError):
print(f" Enter an episode number")
metadata_path = clips_dir / "clips-metadata.json"
if not metadata_path.exists():
print(f"Error: No clips-metadata.json found in {clips_dir}")
print("Run make_clips.py first to generate clips and metadata.")
sys.exit(1)
with open(metadata_path) as f:
clips = json.load(f)
# Pick clips
if args.clip:
if args.clip < 1 or args.clip > len(clips):
print(f"Error: Clip {args.clip} not found (have {len(clips)} clips)")
sys.exit(1)
clips = [clips[args.clip - 1]]
elif not args.yes:
print(f"\nFound {len(clips)} clip(s):\n")
for i, clip in enumerate(clips):
desc = clip.get('description', clip.get('caption_text', ''))
if len(desc) > 70:
desc = desc[:desc.rfind(' ', 0, 70)] + '...'
print(f" {i+1}. \"{clip['title']}\" ({clip['duration']:.0f}s)")
print(f" {desc}")
print(f"\n a. All clips")
print()
while True:
choice = input("Which clips? (e.g. 1,3 or a for all): ").strip().lower()
if choice in ('a', 'all'):
break
try:
indices = [int(x.strip()) for x in choice.split(",")]
if all(1 <= x <= len(clips) for x in indices):
clips = [clips[x - 1] for x in indices]
break
print(f" Invalid selection. Enter 1-{len(clips)}, comma-separated, or 'a' for all.")
except (ValueError, EOFError):
print(f" Enter clip numbers (e.g. 1,3) or 'a' for all")
# Pick platforms
if args.platforms:
requested = []
for p in args.platforms.split(","):
p = p.strip().lower()
if p not in PLATFORM_ALIASES:
print(f"Unknown platform: {p}")
print(f"Valid: {', '.join(valid_names)}")
sys.exit(1)
requested.append(PLATFORM_ALIASES[p])
target_platforms = list(dict.fromkeys(requested))
elif not args.yes:
print(f"\nPlatforms:\n")
for i, p in enumerate(ALL_PLATFORMS):
print(f" {i+1}. {PLATFORM_DISPLAY[p]}")
print(f"\n a. All platforms (default)")
print()
choice = input("Which platforms? (e.g. 1,3,5 or a for all) [a]: ").strip().lower()
if choice and choice not in ('a', 'all'):
try:
indices = [int(x.strip()) for x in choice.split(",")]
target_platforms = [ALL_PLATFORMS[x - 1] for x in indices if 1 <= x <= len(ALL_PLATFORMS)]
if not target_platforms:
target_platforms = ALL_PLATFORMS[:]
except (ValueError, IndexError):
target_platforms = ALL_PLATFORMS[:]
else:
target_platforms = ALL_PLATFORMS[:]
else:
target_platforms = ALL_PLATFORMS[:]
DIRECT_PLATFORMS = {"bluesky", "youtube"}
needs_postiz = not args.dry_run and any(
p not in DIRECT_PLATFORMS for p in target_platforms)
if needs_postiz:
print("Fetching connected accounts from Postiz...")
integrations = fetch_integrations()
else:
integrations = []
active_platforms = {}
for platform in target_platforms:
if platform == "bluesky":
if BSKY_APP_PASSWORD or args.dry_run:
active_platforms[platform] = {"name": BSKY_HANDLE, "_direct": True}
else:
print("Warning: BSKY_APP_PASSWORD not set in .env, skipping Bluesky")
continue
if platform == "youtube":
if YT_CLIENT_SECRETS.exists() or YT_TOKEN_FILE.exists() or args.dry_run:
active_platforms[platform] = {"name": "YouTube Shorts", "_direct": True}
else:
print("Warning: youtube_client_secrets.json not found, skipping YouTube")
continue
if args.dry_run:
active_platforms[platform] = {"name": PLATFORM_DISPLAY[platform]}
continue
integ = find_integration(integrations, platform)
if integ:
active_platforms[platform] = integ
else:
print(f"Warning: No {PLATFORM_DISPLAY[platform]} account connected in Postiz")
if not args.dry_run and not active_platforms:
print("Error: No platforms available to upload to")
sys.exit(1)
platform_names = [f"{PLATFORM_DISPLAY[p]} ({integ.get('name', 'connected')})"
for p, integ in active_platforms.items()]
print(f"\nUploading {len(clips)} clip(s) to: {', '.join(platform_names)}")
if args.schedule:
print(f"Scheduled for: {args.schedule}")
print()
for i, clip in enumerate(clips):
print(f" {i+1}. \"{clip['title']}\" ({clip['duration']:.0f}s)")
desc = clip.get('description', '')
if len(desc) > 80:
desc = desc[:desc.rfind(' ', 0, 80)] + '...'
print(f" {desc}")
print(f" {' '.join(clip.get('hashtags', []))}")
print()
if args.dry_run:
print("Dry run — nothing uploaded.")
return
if not args.yes:
confirm = input("Proceed? [y/N] ").strip().lower()
if confirm != "y":
print("Cancelled.")
return
upload_history = load_upload_history(clips_dir)
for i, clip in enumerate(clips):
clip_file = clips_dir / clip["clip_file"]
if not clip_file.exists():
print(f" Clip {i+1}: Video file not found: {clip_file}")
continue
clip_key = clip["clip_file"]
already_uploaded = set(upload_history.get(clip_key, []))
remaining_platforms = {p: integ for p, integ in active_platforms.items()
if p not in already_uploaded}
if not remaining_platforms:
print(f"\n Clip {i+1}: \"{clip['title']}\" — already uploaded to all selected platforms, skipping")
continue
skipped = already_uploaded & set(active_platforms.keys())
if skipped:
print(f"\n Clip {i+1}: \"{clip['title']}\" (skipping already uploaded: {', '.join(sorted(skipped))})")
else:
print(f"\n Clip {i+1}: \"{clip['title']}\"")
postiz_platforms = {p: integ for p, integ in remaining_platforms.items()
if not integ.get("_direct")}
media = None
if postiz_platforms:
print(f" Uploading {clip_file.name}...")
media = upload_file(clip_file)
if not media:
print(" Failed to upload video to Postiz, skipping Postiz platforms")
postiz_platforms = {}
else:
print(f" Uploaded: {media.get('path', 'ok')}")
for platform, integ in postiz_platforms.items():
display = PLATFORM_DISPLAY[platform]
print(f" Posting to {display}...")
content = build_content(clip, platform)
settings = build_settings(clip, platform)
result = create_post(integ["id"], content, media, settings, args.schedule)
if result:
print(f" {display}: Posted!")
upload_history.setdefault(clip_key, []).append(platform)
save_upload_history(clips_dir, upload_history)
else:
print(f" {display}: Failed")
if "youtube" in remaining_platforms:
print(f" Posting to YouTube Shorts (direct)...")
try:
yt_video_id = post_to_youtube(clip, clip_file)
if yt_video_id:
print(f" YouTube: Posted!")
upload_history.setdefault(clip_key, []).append("youtube")
save_upload_history(clips_dir, upload_history)
else:
print(f" YouTube: Failed")
except Exception as e:
print(f" YouTube: Failed — {e}")
if "bluesky" in remaining_platforms:
print(f" Posting to Bluesky (direct)...")
try:
if post_to_bluesky(clip, clip_file):
print(f" Bluesky: Posted!")
upload_history.setdefault(clip_key, []).append("bluesky")
save_upload_history(clips_dir, upload_history)
else:
print(f" Bluesky: Failed")
except Exception as e:
print(f" Bluesky: Failed — {e}")
# Sync clips to website if any YouTube uploads happened
if "youtube" in active_platforms:
sync_clips_to_website()
print("\nDone!")
WEBSITE_DIR = Path(__file__).parent / "website"
CLIPS_JSON = WEBSITE_DIR / "data" / "clips.json"
THUMBS_DIR = WEBSITE_DIR / "images" / "clips"
CLIPS_ROOT = Path(__file__).parent / "clips"
def sync_clips_to_website():
"""Rebuild website/data/clips.json from YouTube shorts and deploy."""
import subprocess
print("\nSyncing clips to website...")
# Fetch all YouTube shorts from channel
result = subprocess.run(
["python3", "-m", "yt_dlp", "--flat-playlist", "--print", "%(id)s\t%(title)s",
"https://www.youtube.com/lukemacneil/shorts"],
capture_output=True, text=True, timeout=60,
)
if not result.stdout.strip():
print(" Could not fetch YouTube shorts, skipping sync")
return
yt_shorts = {}
for line in result.stdout.strip().split("\n"):
if "\t" not in line:
continue
vid_id, title = line.split("\t", 1)
clean_title = re.sub(r"\s*#Shorts\s*$", "", title).strip().lower()
yt_shorts[clean_title] = vid_id
print(f" Found {len(yt_shorts)} YouTube shorts")
# Load all clip metadata and match to YouTube
existing = {}
if CLIPS_JSON.exists():
for c in json.loads(CLIPS_JSON.read_text()):
existing[c["clip_file"]] = c
new_clips = []
for ep_dir in sorted(CLIPS_ROOT.glob("episode-*")):
meta_file = ep_dir / "clips-metadata.json"
if not meta_file.exists():
continue
for clip in json.loads(meta_file.read_text()):
clip_title = clip["title"].strip().lower()
yt_id = yt_shorts.get(clip_title, "")
if not yt_id:
for yt_title, yid in yt_shorts.items():
if yt_title in clip_title or clip_title in yt_title:
yt_id = yid
break
if not yt_id:
continue
prev = existing.get(clip["clip_file"], {})
new_clips.append({
"title": clip["title"],
"description": clip.get("description", clip.get("caption_text", "")),
"episode_number": clip.get("episode_number", 0),
"clip_file": clip["clip_file"],
"youtube_id": yt_id,
"featured": prev.get("featured", False),
"thumbnail": prev.get("thumbnail", ""),
})
new_clips.sort(key=lambda c: c["episode_number"], reverse=True)
# Generate thumbnails for clips that don't have one
THUMBS_DIR.mkdir(parents=True, exist_ok=True)
for clip in new_clips:
if clip["thumbnail"]:
thumb_path = WEBSITE_DIR / clip["thumbnail"]
if thumb_path.exists():
continue
thumb_name = clip["clip_file"].replace(".mp4", ".jpg")
thumb_path = THUMBS_DIR / thumb_name
ep_num = clip["episode_number"]
mp4_path = CLIPS_ROOT / f"episode-{ep_num}" / clip["clip_file"]
if not mp4_path.exists():
continue
subprocess.run(
["ffmpeg", "-y", "-i", str(mp4_path), "-ss", "3", "-vframes", "1",
"-update", "1", "-vf", "scale=360:-2", "-q:v", "4", str(thumb_path)],
capture_output=True, timeout=30,
)
if thumb_path.exists():
clip["thumbnail"] = f"images/clips/{thumb_name}"
print(f" Generated thumbnail: {thumb_name}")
# Ensure at least 3 featured
featured_count = sum(1 for c in new_clips if c.get("featured"))
if featured_count < 3:
for c in new_clips:
if not c.get("featured"):
c["featured"] = True
featured_count += 1
if featured_count >= 3:
break
CLIPS_JSON.parent.mkdir(parents=True, exist_ok=True)
CLIPS_JSON.write_text(json.dumps(new_clips, indent=2))
print(f" Updated clips.json: {len(new_clips)} clips")
# Deploy
print(" Deploying website...")
deploy = subprocess.run(
["npx", "wrangler", "pages", "deploy", "website/",
"--project-name=lukeattheroost", "--branch=main", "--commit-dirty=true"],
capture_output=True, text=True, timeout=120,
cwd=str(Path(__file__).parent),
)
if "Deployment complete" in deploy.stdout:
print(" Website deployed!")
else:
print(f" Deploy failed: {deploy.stderr[-300:]}")
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
/episodes.html /episode 302
+64
View File
@@ -0,0 +1,64 @@
const VOICEMAIL_XML = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="woman">Luke at the Roost is off the air right now. Leave a message after the beep and we may play it on the next show!</Say>
<Record maxLength="120" action="https://radioshow.macneilmediagroup.com/api/signalwire/voicemail-complete" playBeep="true" />
<Say voice="woman">Thank you for calling. Goodbye!</Say>
<Hangup/>
</Response>`;
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/api/signalwire/voice") {
try {
const body = await request.text();
const resp = await fetch("https://radioshow.macneilmediagroup.com/api/signalwire/voice", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body,
signal: AbortSignal.timeout(5000),
});
if (resp.ok) {
return new Response(await resp.text(), {
status: 200,
headers: { "Content-Type": "application/xml" },
});
}
} catch (e) {
// Server unreachable or timed out
}
return new Response(VOICEMAIL_XML, {
status: 200,
headers: { "Content-Type": "application/xml" },
});
}
// RSS feed proxy
if (url.pathname === "/feed") {
try {
const resp = await fetch("https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml", {
signal: AbortSignal.timeout(8000),
});
if (resp.ok) {
return new Response(await resp.text(), {
status: 200,
headers: {
"Content-Type": "application/xml",
"Access-Control-Allow-Origin": "*",
"Cache-Control": "public, max-age=300",
},
});
}
} catch (e) {
// Castopod unreachable
}
return new Response("Feed unavailable", { status: 502 });
}
// All other requests — serve static assets
return env.ASSETS.fetch(request);
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+100
View File
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clips — Luke at the Roost</title>
<meta name="description" content="The best moments from Luke at the Roost — watch clips from the AI call-in comedy podcast.">
<meta name="theme-color" content="#1a1209">
<link rel="canonical" href="https://lukeattheroost.com/clips">
<meta property="og:site_name" content="Luke at the Roost">
<meta property="og:title" content="Clips — Luke at the Roost">
<meta property="og:description" content="The best moments from Luke at the Roost — watch clips from the AI call-in comedy podcast.">
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<meta property="og:url" content="https://lukeattheroost.com/clips">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Clips — Luke at the Roost">
<meta name="twitter:description" content="The best moments from Luke at the Roost — watch clips from the AI call-in comedy podcast.">
<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/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="alternate" type="application/rss+xml" title="Luke at the Roost RSS Feed" href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml">
<link rel="stylesheet" href="css/style.css?v=3">
</head>
<body>
<nav class="page-nav">
<a href="/" class="nav-home">Luke at the Roost</a>
</nav>
<section class="page-header">
<h1>Clips</h1>
<p class="page-subtitle">The best moments from the show</p>
</section>
<div class="clips-section-header">
<h2>Featured</h2>
</div>
<section class="clips-featured"></section>
<div class="clips-section-header">
<h2>All Clips</h2>
</div>
<section class="clips-grid"></section>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/how-it-works">How It Works</a>
<a href="/clips">Clips</a>
<a href="/stats">Stats</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
<script src="js/clips.js"></script>
</body>
</html>
+1249 -53
View File
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
[
{
"title": "Man Obsessed With Dead Nun Loses Wife",
"description": "Rodney couldn't stop talking about a dead nun who shared his wife's name. His wife was NOT amused.",
"episode_number": 33,
"clip_file": "clip-1-man-obsessed-with-dead-nun-loses-wife.mp4",
"youtube_id": "zD8CdX7s8us",
"featured": false,
"thumbnail": "images/clips/clip-1-man-obsessed-with-dead-nun-loses-wife.jpg"
},
{
"title": "I Faked Cancer to Skip a Wedding",
"description": "A small lie to skip his sister's FOURTH wedding spiraled into a GoFundMe, a pancake breakfast fundraiser, and a $4,700 check. Now he's in too deep.",
"episode_number": 32,
"clip_file": "clip-1-i-faked-cancer-to-skip-a-wedding.mp4",
"youtube_id": "NUkhsPfMx9o",
"featured": true,
"thumbnail": "images/clips/clip-1-i-faked-cancer-to-skip-a-wedding.jpg"
},
{
"title": "Started a Fight and Can't Stop Reading About Wars",
"description": "",
"episode_number": 31,
"clip_file": "clip-3-started-a-fight-and-can-t-stop-reading-about-wars.mp4",
"youtube_id": "D2iWnSGQeow",
"featured": false,
"thumbnail": "images/clips/clip-3-started-a-fight-and-can-t-stop-reading-about-wars.jpg"
},
{
"title": "Latex Fetish Confession Goes Silent",
"description": "He finally opened up about his latex fetish to his girlfriend and got 10 seconds of pure silence. Now he's wondering if honesty just cost him everything.",
"episode_number": 30,
"clip_file": "clip-3-latex-fetish-confession-goes-silent.mp4",
"youtube_id": "vFvWUbYacug",
"featured": false,
"thumbnail": "images/clips/clip-3-latex-fetish-confession-goes-silent.jpg"
},
{
"title": "Adopted Daughter Might Have Been Stolen",
"description": "A lawyer in Guatemala just sent him proof that his adopted daughter might have been stolen from her birth mother. The photo shows the same crooked smile and now everything is falling apart.",
"episode_number": 29,
"clip_file": "clip-3-adopted-daughter-might-have-been-stolen.mp4",
"youtube_id": "hMxldZN3VHw",
"featured": false,
"thumbnail": "images/clips/clip-3-adopted-daughter-might-have-been-stolen.jpg"
},
{
"title": "Vinyl vs Digital: The Warmth Debate",
"description": "Vinyl collector calls in to absolutely torch the 'warmth' argument. Turns out what you love about records might just be... imperfection.",
"episode_number": 28,
"clip_file": "clip-2-vinyl-vs-digital-the-warmth-debate.mp4",
"youtube_id": "aVmhApa0d2E",
"featured": false,
"thumbnail": "images/clips/clip-2-vinyl-vs-digital-the-warmth-debate.jpg"
},
{
"title": "Open Marriage Backfired Spectacularly",
"description": "She thought opening up the marriage would spice things up. He's living his best life while she can't even get a text back.",
"episode_number": 27,
"clip_file": "clip-2-open-marriage-backfired-spectacularly.mp4",
"youtube_id": "-K-t7iijfGs",
"featured": false,
"thumbnail": "images/clips/clip-2-open-marriage-backfired-spectacularly.jpg"
},
{
"title": "Neighbor's Roomba Breaks Into Kitchen at 2:30 AM",
"description": "She woke up at 2:30 AM to find her neighbor Gary's Roomba cleaning her kitchen. Yes, it had Gary's name on it. Yes, it came through the dog door.",
"episode_number": 26,
"clip_file": "clip-2-neighbor-s-roomba-breaks-into-kitchen-at-2-30-am.mp4",
"youtube_id": "J7bfT6jsykA",
"featured": true,
"thumbnail": "images/clips/clip-2-neighbor-s-roomba-breaks-into-kitchen-at-2-30-am.jpg"
},
{
"title": "You're a Computer-Generated AI Caller",
"description": "The AI caller admits it: 'You're right. I am computer-generated. And you're sitting there alone at midnight talking to me like it's real.' Luke's reaction is everything.",
"episode_number": 24,
"clip_file": "clip-10-you-re-a-computer-generated-ai-caller.mp4",
"youtube_id": "QVcoU59OTRA",
"featured": false,
"thumbnail": "images/clips/clip-10-you-re-a-computer-generated-ai-caller.jpg"
},
{
"title": "Full Banana Costume at Work Party",
"description": "He showed up to his manager's holiday party in a full banana costume and now HR says it shows 'lack of professional judgment.' Was it really that serious?",
"episode_number": 23,
"clip_file": "clip-4-full-banana-costume-at-work-party.mp4",
"youtube_id": "enyqXHxmzqA",
"featured": false,
"thumbnail": "images/clips/clip-4-full-banana-costume-at-work-party.jpg"
},
{
"title": "The Poison That Saved Everything",
"description": "The apocalypse that saved us all. 2.5 billion years ago, oxygen poisoned nearly everything on Earth\u2014and that catastrophe made life as we know it possible.",
"episode_number": 22,
"clip_file": "clip-1-the-poison-that-saved-everything.mp4",
"youtube_id": "BHm3RJ5YC_8",
"featured": false,
"thumbnail": "images/clips/clip-1-the-poison-that-saved-everything.jpg"
},
{
"title": "I Burned My Marriage for Work",
"description": "He burned his second marriage to the ground chasing work. His wife didn't leave because of money\u2014she left because he was never there.",
"episode_number": 22,
"clip_file": "clip-3-i-burned-my-marriage-for-work.mp4",
"youtube_id": "1a_9Yl-isN0",
"featured": false,
"thumbnail": "images/clips/clip-3-i-burned-my-marriage-for-work.jpg"
},
{
"title": "Shopping Cart Theory: Moral Test or Crazy?",
"description": "A caller defends the shopping cart theory by admitting they literally watch people from their workplace. Luke has some thoughts on what's actually crazy here.",
"episode_number": 21,
"clip_file": "clip-2-shopping-cart-theory-moral-test-or-crazy.mp4",
"youtube_id": "KijyJsMZfkA",
"featured": true,
"thumbnail": "images/clips/clip-2-shopping-cart-theory-moral-test-or-crazy.jpg"
},
{
"title": "Dog Takes a Shit With Leg Up",
"description": "This caller's dog just unlocked a new achievement that has him bursting with pride. You won't believe what had him celebrating like his pup won best in show.",
"episode_number": 20,
"clip_file": "clip-1-dog-takes-a-shit-with-leg-up.mp4",
"youtube_id": "SXcCrlQTuwM",
"featured": false,
"thumbnail": "images/clips/clip-1-dog-takes-a-shit-with-leg-up.jpg"
},
{
"title": "Don't Get Married PSA",
"description": "A brutal take on marriage from someone who learned the hard way. This caller is not holding back with the relationship advice tonight.",
"episode_number": 19,
"clip_file": "clip-2-don-t-get-married-psa.mp4",
"youtube_id": "6pKcYXgzizs",
"featured": false,
"thumbnail": "images/clips/clip-2-don-t-get-married-psa.jpg"
},
{
"title": "Signed Into an Illegal Poker Game",
"description": "Imagine walking into an illegal poker game and they make you sign a guest book with your real info. The audacity is actually impressive.",
"episode_number": 18,
"clip_file": "clip-2-signed-into-an-illegal-poker-game.mp4",
"youtube_id": "eCT0gUVLlbA",
"featured": false,
"thumbnail": "images/clips/clip-2-signed-into-an-illegal-poker-game.jpg"
},
{
"title": "We're Dinner for a Black Hole",
"description": "This caller has a theory that we're all just corn on the cob waiting to be eaten by a distant black hole. Can't stop thinking about it now.",
"episode_number": 17,
"clip_file": "clip-2-we-re-dinner-for-a-black-hole.mp4",
"youtube_id": "lakicW3cbPw",
"featured": false,
"thumbnail": "images/clips/clip-2-we-re-dinner-for-a-black-hole.jpg"
},
{
"title": "Maybe You Should Eat More of Her",
"description": "Luke doesn't hold back when a caller complains about his long-distance girlfriend not visiting enough. This relationship advice is absolutely WILD and you need to hear it.",
"episode_number": 16,
"clip_file": "clip-1-maybe-you-should-eat-more-of-her.mp4",
"youtube_id": "P5UNb_njsig",
"featured": false,
"thumbnail": "images/clips/clip-1-maybe-you-should-eat-more-of-her.jpg"
},
{
"title": "I Lied About Speaking Spanish for 8 Years",
"description": "This crop duster faked speaking Spanish to get hired and somehow kept the lie going for EIGHT YEARS. Now he's got a 3-week contract in Mexico City and he's about to get exposed.",
"episode_number": 14,
"clip_file": "clip-1-i-lied-about-speaking-spanish-for-8-years.mp4",
"youtube_id": "MxDjohJEneQ",
"featured": false,
"thumbnail": "images/clips/clip-1-i-lied-about-speaking-spanish-for-8-years.jpg"
},
{
"title": "You Can't Have It Both Ways",
"description": "This caller goes OFF on absent parents who use work as an excuse. Just because you're making money on the road doesn't mean you're present in your kid's life.",
"episode_number": 13,
"clip_file": "clip-2-you-can-t-have-it-both-ways.mp4",
"youtube_id": "Or62RF0uJQM",
"featured": false,
"thumbnail": "images/clips/clip-2-you-can-t-have-it-both-ways.jpg"
},
{
"title": "Pretending to Buy Houses for 8 Months",
"description": "For 8 months, this caller has been attending open houses every weekend pretending to be different people. They're not buying... they're just living fake lives for fun.",
"episode_number": 13,
"clip_file": "clip-3-pretending-to-buy-houses-for-8-months.mp4",
"youtube_id": "VSWknaHx7d0",
"featured": false,
"thumbnail": "images/clips/clip-3-pretending-to-buy-houses-for-8-months.jpg"
},
{
"title": "No More Thursdays in Deming",
"description": "Rita thought she was being sneaky meeting her ex every Thursday at a Deming motel... until her husband started tracking the mileage. Luke's response? Absolutely brutal.",
"episode_number": 11,
"clip_file": "clip-1-no-more-thursdays-in-deming.mp4",
"youtube_id": "weeDjSi7vuc",
"featured": false,
"thumbnail": "images/clips/clip-1-no-more-thursdays-in-deming.jpg"
},
{
"title": "Cemetery Widow Stalker",
"description": "A cemetery groundskeeper thought a widow was mourning her husband... until he realized she keeps showing up at midnight asking his COWORKERS about him. This one gets creepy fast.",
"episode_number": 8,
"clip_file": "clip-2-cemetery-widow-stalker.mp4",
"youtube_id": "dAiM1WT5-_A",
"featured": false,
"thumbnail": "images/clips/clip-2-cemetery-widow-stalker.jpg"
}
]
+343
View File
@@ -0,0 +1,343 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#1a1209">
<title id="page-title">Episode — Luke at the Roost</title>
<meta name="description" id="page-description" content="Full transcript of this episode of Luke at the Roost, the late-night call-in radio show.">
<link rel="canonical" id="page-canonical" href="https://lukeattheroost.com/episode.html">
<!-- OG / Social -->
<meta property="og:site_name" content="Luke at the Roost">
<meta property="og:title" id="og-title" content="Episode — Luke at the Roost">
<meta property="og:description" id="og-description" content="Full transcript of this episode of Luke at the Roost.">
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<meta property="og:url" id="og-url" content="https://lukeattheroost.com/episode.html">
<meta property="og:type" content="article">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" id="tw-title" content="Episode — Luke at the Roost">
<meta name="twitter:description" id="tw-description" content="Full transcript of this episode of Luke at the Roost.">
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<!-- Favicon -->
<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="alternate" type="application/rss+xml" title="Luke at the Roost RSS Feed" href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml">
<link rel="stylesheet" href="css/style.css?v=3">
<!-- Structured Data (dynamically updated by JS) -->
<script type="application/ld+json" id="episode-jsonld">
{
"@context": "https://schema.org",
"@type": "PodcastEpisode",
"partOfSeries": {
"@type": "PodcastSeries",
"name": "Luke at the Roost",
"url": "https://lukeattheroost.com"
},
"name": "Episode — Luke at the Roost",
"url": "https://lukeattheroost.com/episode.html",
"description": "Full transcript of this episode of Luke at the Roost.",
"inLanguage": "en"
}
</script>
</head>
<body>
<!-- Nav -->
<nav class="page-nav">
<a href="/" class="nav-home">&larr; Luke at the Roost</a>
</nav>
<!-- Episode Header -->
<section class="ep-header" id="ep-header">
<div class="ep-header-inner">
<div class="ep-meta" id="ep-meta"></div>
<h1 class="ep-title" id="ep-title">Loading...</h1>
<p class="ep-desc" id="ep-desc"></p>
<div class="ep-actions">
<button class="ep-play-btn" id="ep-play-btn" style="display:none" aria-label="Play Episode">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<span>Play Episode</span>
</button>
</div>
</div>
</section>
<!-- Transcript -->
<section class="transcript-section" id="transcript-section">
<h2>Full Transcript</h2>
<div class="transcript-body" id="transcript-body">
<div class="episodes-loading">Loading transcript...</div>
</div>
</section>
<noscript>
<section class="transcript-section">
<p>This page requires JavaScript to load the episode transcript. Please enable JavaScript or listen on <a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF">Spotify</a>, <a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848">Apple Podcasts</a>, or <a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-">YouTube</a>.</p>
</section>
</noscript>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/">Home</a>
<a href="/how-it-works">How It Works</a>
<a href="/stats">Stats</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
<!-- Sticky Audio Player -->
<div class="sticky-player" id="sticky-player">
<div class="player-inner">
<button class="player-play-btn" id="player-play-btn" aria-label="Play/Pause">
<svg class="icon-play" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
<svg class="icon-pause" viewBox="0 0 24 24" style="display:none"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
</button>
<div class="player-info">
<div class="player-title" id="player-title"></div>
<div class="player-progress-row">
<div class="player-progress" id="player-progress">
<div class="player-progress-fill" id="player-progress-fill"></div>
</div>
<span class="player-time" id="player-time">0:00 / 0:00</span>
</div>
</div>
</div>
</div>
<audio id="audio-element" preload="none"></audio>
<script>
const FEED_URL = '/feed';
const CDN_BASE = 'https://cdn.lukeattheroost.com';
const audio = document.getElementById('audio-element');
const stickyPlayer = document.getElementById('sticky-player');
const playerPlayBtn = document.getElementById('player-play-btn');
const playerTitle = document.getElementById('player-title');
const playerProgress = document.getElementById('player-progress');
const playerProgressFill = document.getElementById('player-progress-fill');
const playerTime = document.getElementById('player-time');
function formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '0:00';
const s = Math.floor(seconds);
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
return `${m}:${String(sec).padStart(2, '0')}`;
}
function formatDate(dateStr) {
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function parseDuration(raw) {
if (!raw) return '';
if (raw.includes(':')) {
const parts = raw.split(':').map(Number);
let t = 0;
if (parts.length === 3) t = parts[0]*3600 + parts[1]*60 + parts[2];
else if (parts.length === 2) t = parts[0]*60 + parts[1];
return `${Math.round(t/60)} min`;
}
const sec = parseInt(raw, 10);
return isNaN(sec) ? '' : `${Math.round(sec/60)} min`;
}
function stripHtml(html) {
const div = document.createElement('div');
div.innerHTML = html || '';
return div.textContent || '';
}
// Get slug from URL
const params = new URLSearchParams(window.location.search);
const slug = params.get('slug');
if (!slug) {
document.getElementById('ep-title').textContent = 'Episode not found';
document.getElementById('transcript-body').innerHTML = '<p>No episode specified. <a href="/">Go back to episodes.</a></p>';
} else {
loadEpisode(slug);
}
async function loadEpisode(slug) {
// Fetch episode info from RSS
try {
const res = await fetch(FEED_URL);
const xml = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(xml, 'text/xml');
const items = doc.querySelectorAll('item');
let episode = null;
for (const item of items) {
const link = item.querySelector('link')?.textContent || '';
const itemSlug = link.split('/episodes/').pop()?.replace(/\/$/, '');
if (itemSlug === slug) {
episode = {
title: item.querySelector('title')?.textContent || 'Untitled',
description: item.querySelector('description')?.textContent || '',
audioUrl: item.querySelector('enclosure')?.getAttribute('url') || '',
pubDate: item.querySelector('pubDate')?.textContent || '',
duration: item.getElementsByTagNameNS('http://www.itunes.com/dtds/podcast-1.0.dtd', 'duration')[0]?.textContent || '',
episodeNum: item.getElementsByTagNameNS('http://www.itunes.com/dtds/podcast-1.0.dtd', 'episode')[0]?.textContent || '',
};
break;
}
}
if (!episode) {
document.getElementById('ep-title').textContent = 'Episode not found';
document.getElementById('transcript-body').innerHTML = '<p>Could not find this episode. <a href="/">Go back to episodes.</a></p>';
return;
}
// Populate header
const metaParts = [
episode.episodeNum ? `Episode ${episode.episodeNum}` : '',
episode.pubDate ? formatDate(episode.pubDate) : '',
parseDuration(episode.duration),
].filter(Boolean).join(' \u00b7 ');
document.getElementById('ep-meta').textContent = metaParts;
document.getElementById('ep-title').textContent = episode.title;
document.getElementById('ep-desc').innerHTML = episode.description || '';
// Update page meta
document.title = `${episode.title} — Luke at the Roost`;
document.getElementById('page-description')?.setAttribute('content', `Full transcript of ${episode.title} from Luke at the Roost.`);
document.getElementById('og-title')?.setAttribute('content', episode.title);
document.getElementById('og-description')?.setAttribute('content', stripHtml(episode.description).slice(0, 200));
const canonicalUrl = `https://lukeattheroost.com/episode.html?slug=${slug}`;
document.getElementById('page-canonical')?.setAttribute('href', canonicalUrl);
document.getElementById('og-url')?.setAttribute('content', canonicalUrl);
document.getElementById('tw-title')?.setAttribute('content', episode.title);
document.getElementById('tw-description')?.setAttribute('content', stripHtml(episode.description).slice(0, 200));
// Update JSON-LD structured data
const jsonLd = document.getElementById('episode-jsonld');
if (jsonLd) {
const ld = JSON.parse(jsonLd.textContent);
ld.name = episode.title;
ld.url = canonicalUrl;
ld.description = stripHtml(episode.description).slice(0, 300);
if (episode.pubDate) ld.datePublished = new Date(episode.pubDate).toISOString().split('T')[0];
if (episode.episodeNum) ld.episodeNumber = parseInt(episode.episodeNum, 10);
if (episode.audioUrl) {
ld.associatedMedia = {
"@type": "MediaObject",
"contentUrl": episode.audioUrl
};
}
jsonLd.textContent = JSON.stringify(ld);
}
// Play button
if (episode.audioUrl) {
const playBtn = document.getElementById('ep-play-btn');
playBtn.style.display = 'inline-flex';
playBtn.addEventListener('click', () => {
audio.src = episode.audioUrl;
audio.play();
playerTitle.textContent = episode.title;
stickyPlayer.classList.add('active');
});
}
} catch (e) {
document.getElementById('ep-title').textContent = 'Error loading episode';
}
// Fetch transcript
try {
const txRes = await fetch(`/transcripts/${slug}.txt`);
if (!txRes.ok) throw new Error('Not found');
const text = await txRes.text();
const paragraphs = text.split(/\n\n+/).filter(Boolean);
const html = paragraphs.map(p => {
// Style speaker labels (LUKE:, REGGIE:, etc.)
const labeled = p.replace(/^([A-Z][A-Z\s'-]+?):\s*/, '<span class="speaker-label">$1:</span> ');
return `<p>${labeled.replace(/\n/g, '<br>')}</p>`;
}).join('');
document.getElementById('transcript-body').innerHTML = html;
} catch (e) {
document.getElementById('transcript-body').innerHTML = '<p class="transcript-unavailable">Transcript not yet available for this episode.</p>';
}
}
// Audio player controls
audio.addEventListener('play', () => updatePlayIcons(true));
audio.addEventListener('pause', () => updatePlayIcons(false));
audio.addEventListener('ended', () => updatePlayIcons(false));
audio.addEventListener('timeupdate', () => {
if (audio.duration) {
playerProgressFill.style.width = (audio.currentTime / audio.duration * 100) + '%';
playerTime.textContent = `${formatTime(audio.currentTime)} / ${formatTime(audio.duration)}`;
}
});
function updatePlayIcons(playing) {
const iconPlay = playerPlayBtn.querySelector('.icon-play');
const iconPause = playerPlayBtn.querySelector('.icon-pause');
if (iconPlay) iconPlay.style.display = playing ? 'none' : 'block';
if (iconPause) iconPause.style.display = playing ? 'block' : 'none';
}
playerPlayBtn.addEventListener('click', () => {
if (audio.src) { audio.paused ? audio.play() : audio.pause(); }
});
playerProgress.addEventListener('click', (e) => {
if (audio.duration) {
const rect = playerProgress.getBoundingClientRect();
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
}
});
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

+35
View File
@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Dark background circle -->
<circle cx="32" cy="32" r="30" fill="#1a1209"/>
<!-- Neon rooster silhouette -->
<g filter="url(#glow)" fill="none" stroke="#e8791d" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<!-- Comb -->
<path d="M30 12c1-3 3-5 5-5s3 2 3 4c0-2 2-3 3-2s1 3 0 5"/>
<!-- Head -->
<path d="M30 12c-2 1-4 4-4 7 0 3 2 6 6 7"/>
<path d="M41 19c0-3-2-6-5-7"/>
<!-- Eye -->
<circle cx="33" cy="16" r="1.5" fill="#e8791d"/>
<!-- Beak -->
<path d="M26 17l-3 1 3 2"/>
<!-- Body -->
<path d="M32 26c-5 1-9 5-10 10l-1 6c0 1 1 2 2 2h3"/>
<path d="M32 26c5 1 9 5 10 10l1 6c0 1-1 2-2 2h-3"/>
<!-- Tail feathers -->
<path d="M42 30c3-4 6-8 7-14"/>
<path d="M43 32c4-3 8-5 10-10"/>
<path d="M44 35c4-1 7-2 9-5"/>
<!-- Legs -->
<path d="M27 44l-2 8M25 52l-3 1M25 52l2 1"/>
<path d="M37 44l2 8M39 52l-2 1M39 52l3 1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+685
View File
@@ -0,0 +1,685 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>How It Works — Luke at the Roost</title>
<meta name="description" content="How Luke at the Roost works: AI-generated callers with unique personalities, real phone calls, voice synthesis, multi-stem recording, and automated post-production.">
<meta name="theme-color" content="#1a1209">
<link rel="canonical" href="https://lukeattheroost.com/how-it-works">
<meta property="og:site_name" content="Luke at the Roost">
<meta property="og:title" content="How It Works — Luke at the Roost">
<meta property="og: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 — all custom-built.">
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<meta property="og:url" content="https://lukeattheroost.com/how-it-works">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="How It Works — Luke at the Roost">
<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">
<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="alternate" type="application/rss+xml" title="Luke at the Roost RSS Feed" href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml">
<link rel="stylesheet" href="css/style.css?v=3">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "How Luke at the Roost Works",
"description": "How Luke at the Roost works: AI-generated callers with unique personalities, real phone calls, voice synthesis, multi-stem recording, and automated post-production.",
"url": "https://lukeattheroost.com/how-it-works",
"image": "https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3",
"author": {
"@type": "Person",
"name": "Luke MacNeil"
},
"publisher": {
"@type": "Organization",
"name": "MacNeil Media Group"
},
"about": {
"@type": "PodcastSeries",
"name": "Luke at the Roost",
"url": "https://lukeattheroost.com"
},
"inLanguage": "en"
}
</script>
</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>How It Works</h1>
<p class="page-subtitle">Every caller on the show is a one-of-a-kind character — generated in real time by a custom-built AI system. Here's a peek behind the curtain.</p>
</section>
<!-- Steps -->
<section class="hiw-section">
<h2>The Anatomy of an AI Caller</h2>
<div class="hiw-steps">
<div class="hiw-step">
<div class="hiw-step-number">1</div>
<div class="hiw-step-content">
<h3>A Person Is Born</h3>
<p>Every caller starts as a blank slate. The system generates a complete identity: name, age, job, hometown, and personality. Each caller gets a unique speaking style — some ramble, some are blunt, some deflect with humor. They have relationships, vehicles, strong food opinions, nostalgic memories, and reasons for being up this late. They know what they were watching on TV, what errand they ran today, and what song was on the radio before they called.</p>
<p>Some callers become regulars. The system tracks returning callers across episodes — they remember past conversations, reference things they talked about before, and their stories evolve over time. You'll hear Leon check in about going back to school, or Shaniqua update you on her situation at work. They're not reset between shows.</p>
<p>And some callers are drunk, high, or flat-out unhinged. They'll call with conspiracy theories about pigeons being government drones, existential crises about whether fish know they're wet, or to confess they accidentally set their kitchen on fire trying to make grilled cheese at 3 AM.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Unique Names</span>
<span class="hiw-detail-value">160</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Personality Layers</span>
<span class="hiw-detail-value">300+</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Towns with Real Knowledge</span>
<span class="hiw-detail-value">55</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Returning Regulars</span>
<span class="hiw-detail-value">12 callers</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">2</div>
<div class="hiw-step-content">
<h3>They Know Their World</h3>
<p>Callers know real facts about where they live — the restaurants, the highways, the local gossip. The system has deep knowledge of 55 real towns across New Mexico and Arizona. When a caller says they're from Lordsburg, they actually know about the Shakespeare ghost town and the drive to Deming. They know the current weather outside their window, what day of the week it is, whether it's monsoon season or chile harvest. They have strong opinions about where to get the best green chile and get nostalgic about how their town used to be. The system also pulls in real-time news so callers can reference things that actually happened today.</p>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">3</div>
<div class="hiw-step-content">
<h3>They Have a Reason to Call</h3>
<p>Some callers have a problem — a fight with a neighbor, a situation at work, something weighing on them at 2 AM. Others call to geek out about Severance, argue about poker strategy, or share something they read about quantum physics. The system draws from over 1,000 unique calling reasons across dozens of categories — problems, stories, advice-seeking, gossip, and deep-dive topics. Every caller has a purpose, not just a script.</p>
<div class="hiw-split-stat">
<div class="hiw-stat">
<span class="hiw-stat-number">70%</span>
<span class="hiw-stat-label">Need advice</span>
</div>
<div class="hiw-stat">
<span class="hiw-stat-number">30%</span>
<span class="hiw-stat-label">Want to talk about something</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">4</div>
<div class="hiw-step-content">
<h3>The Conversation Is Real</h3>
<p>Luke talks to each caller using push-to-talk, just like a real radio show. His voice is transcribed in real time, sent to an AI that responds in character, and then converted to speech using a voice engine — all in a few seconds. The AI doesn't just answer questions; it reacts, gets emotional, goes on tangents, and remembers what was said earlier in the show. Callers even react to previous callers — "Hey Luke, I heard that guy Tony earlier and I got to say, he's full of it." It makes the show feel like a living community, not isolated calls.</p>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">5</div>
<div class="hiw-step-content">
<h3>Real Callers Call In Too</h3>
<p>When you dial 208-439-LUKE, your call goes into a live queue. Luke sees you waiting and can take your call right from the control room. Your voice streams in real time — no pre-recording, no delay. You're live on the show, talking to Luke, and the AI callers might even react to what you said. And if Luke isn't live, you can leave a voicemail — it gets transcribed and may get played on a future episode.</p>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">6</div>
<div class="hiw-step-content">
<h3>Listener Emails</h3>
<p>Listeners can send emails to <a href="mailto:submissions@lukeattheroost.com" style="color:var(--accent)">submissions@lukeattheroost.com</a> and have them read on the show. A background poller checks for new messages every 30 seconds — they show up in the control room as soon as they arrive. Luke can read them himself on the mic, or hit a button to have an AI voice read them aloud on the caller channel. It's like a call-in show meets a letters segment — listeners who can't call in can still be part of the conversation.</p>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">7</div>
<div class="hiw-step-content">
<h3>The Control Room</h3>
<p>The entire show runs through a custom-built control panel. Luke manages callers, plays music and sound effects, runs ads and station idents, monitors the call queue, and controls everything from one screen. Audio is routed across seven independent channels simultaneously — host mic, AI caller voices, live phone audio, music, sound effects, ads, and station idents all on separate tracks. The website shows a live on-air indicator so listeners know when to call in.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Audio Channels</span>
<span class="hiw-detail-value">7 independent</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Caller Slots</span>
<span class="hiw-detail-value">10 per session</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Phone System</span>
<span class="hiw-detail-value">VoIP + WebSocket</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Live Status</span>
<span class="hiw-detail-value">Real-time CDN</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Overview -->
<section class="hiw-section">
<div class="hiw-card hiw-hero-card">
<div class="hiw-diagram">
<!-- Row 1: Inputs -->
<div class="diagram-label">Live Show</div>
<div class="diagram-row diagram-row-split">
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
</div>
<span>Luke (Host)</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
</div>
<span>AI Callers</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
</div>
<span>Real Callers</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="16" rx="2"/><path d="M2 6l10 7 10-7"/></svg>
</div>
<span>Voicemails</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
</div>
<span>Listener Emails</span>
</div>
</div>
<div class="diagram-arrow">&#8595;</div>
<!-- Row 2: Control Room -->
<div class="diagram-row">
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
</div>
<span>Control Room</span>
</div>
</div>
<div class="diagram-arrow">&#8595;</div>
<!-- Row 3: Engine Layer -->
<div class="diagram-row diagram-row-split">
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
</div>
<span>LLM Dialog</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/></svg>
</div>
<span>Voice Synthesis</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
</div>
<span>Live Data</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
</div>
<span>Audio Router</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
</div>
<span>Phone System</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
</div>
<span>Ad Engine</span>
</div>
</div>
<!-- Row 4: Recording -->
<div class="diagram-row">
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>
</div>
<span>Multi-Stem Recorder</span>
</div>
</div>
<div class="diagram-arrow">&#8595;</div>
<!-- Row 5: Post-Production -->
<div class="diagram-label">Post-Production</div>
<div class="diagram-row diagram-row-split">
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
</div>
<span>Compression &amp; Ducking</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</div>
<span>Loudness Normalization</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<span>Transcription</span>
</div>
</div>
<div class="diagram-arrow">&#8595;</div>
<!-- Row 6: Publishing -->
<div class="diagram-label">Publishing</div>
<div class="diagram-row diagram-row-split">
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>
</div>
<span>Podcast Server</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<span>CDN Edge Network</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
</div>
<span>Website</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="11" rx="2"/><path d="M7 21h10"/><path d="M12 14v7"/><polygon points="10 8 16 11 10 14 10 8"/></svg>
</div>
<span>Social Clips</span>
</div>
<div class="diagram-box">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<span>Monitoring</span>
</div>
</div>
<div class="diagram-arrow">&#8595;</div>
<!-- Row 7: Distribution -->
<div class="diagram-label">Distribution</div>
<div class="diagram-row-compact">
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<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>
</div>
<span>Spotify</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<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 0z"/></svg>
</div>
<span>Apple</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#fff"/></svg>
</div>
<span>YouTube</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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.248z"/><path d="M.002 9.473v4.594c5.508.163 9.929 4.584 10.092 10.091h4.594"/><path d="M.006 0v4.604C10.81 4.77 19.23 13.19 19.396 24h4.604"/></svg>
</div>
<span>RSS</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm3.1 14.5c-1.7 1-3.8.6-4.8-1.1-1-1.7-.6-3.8 1.1-4.8 1.7-1 3.8-.6 4.8 1.1 1 1.7.5 3.8-1.1 4.8z"/></svg>
</div>
<span>Instagram</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</div>
<span>Facebook</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg>
</div>
<span>Bluesky</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#fff"/></svg>
</div>
<span>Mastodon</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg>
</div>
<span>Nostr</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
</div>
<span>LinkedIn</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.159 2c-5.543 0-9.94 4.486-9.94 9.94 0 2.16.72 4.148 1.897 5.852l-1.26 4.463 4.612-1.188A9.882 9.882 0 0 0 12.16 22c5.543 0 9.94-4.486 9.94-9.94 0-5.543-4.486-10.06-9.94-10.06z"/></svg>
</div>
<span>Threads</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>
</div>
<span>TikTok</span>
</div>
<div class="diagram-box diagram-accent">
<div class="diagram-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
</div>
<span>Analytics</span>
</div>
</div>
</div>
</div>
</section>
<!-- Post-Production Pipeline -->
<section class="hiw-section">
<h2>From Live Show to Podcast</h2>
<div class="hiw-steps">
<div class="hiw-step">
<div class="hiw-step-number">8</div>
<div class="hiw-step-content">
<h3>Multi-Stem Recording</h3>
<p>During every show, the system records six separate audio stems simultaneously: host microphone, AI caller voices, music, sound effects, ads, and station idents. Each stem is captured as an independent WAV file with sample-accurate alignment. This gives full control over the final mix — like having a recording studio's multitrack session, not just a flat recording.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Stems Captured</span>
<span class="hiw-detail-value">6 parallel</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Format</span>
<span class="hiw-detail-value">48kHz WAV</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Sync Method</span>
<span class="hiw-detail-value">Time-aligned</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Architecture</span>
<span class="hiw-detail-value">Lock-free I/O</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">9</div>
<div class="hiw-step-content">
<h3>Dialog Editing in REAPER</h3>
<p>Before the automated pipeline runs, the raw stems are loaded into REAPER for dialog editing. A custom Lua script analyzes voice tracks to detect silence gaps — the dead air between caller responses, TTS latency pauses, and gaps where Luke is reading the control room. The script strips these silences and ripple-edits all tracks in sync so ads, idents, and music shift with the dialog cuts. Protected regions marked as ads or idents are preserved — the script knows not to remove silence during an ad break even if the voice tracks are quiet. This tightens a raw two-hour session into a focused episode without cutting any content.</p>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">10</div>
<div class="hiw-step-content">
<h3>Post-Production Pipeline</h3>
<p>Once the show ends, a 15-step automated pipeline processes the raw stems into a broadcast-ready episode. Ads and sound effects are hard-limited to prevent clipping. The host mic gets a high-pass filter, de-essing, and breath reduction. Voice tracks are compressed — the host gets aggressive spoken-word compression for consistent levels, callers get telephone EQ to sound like real phone calls. All stems are level-matched, music is ducked under dialog and muted during ads, then everything is mixed to stereo with panning and width. A bus compressor glues the final mix together before silence trimming, fades, and EBU R128 loudness normalization.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Pipeline Steps</span>
<span class="hiw-detail-value">15</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Loudness Target</span>
<span class="hiw-detail-value">-16 LUFS</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Loudness Range</span>
<span class="hiw-detail-value">~5.5 LU</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Output</span>
<span class="hiw-detail-value">Stereo MP3</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">11</div>
<div class="hiw-step-content">
<h3>Automated Publishing</h3>
<p>A single command takes a finished episode and handles everything: the audio is transcribed using MLX Whisper running on Apple Silicon GPU to generate full-text transcripts, then an LLM analyzes the transcript to write the episode title, description, and chapter markers with timestamps. The episode is uploaded to the podcast server and directly to YouTube with chapters baked into the description. Chapters and transcripts are attached to the RSS metadata, all media is synced to a global CDN, and social posts are pushed to eight platforms — all from one command.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Transcription</span>
<span class="hiw-detail-value">MLX Whisper (GPU)</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Metadata</span>
<span class="hiw-detail-value">LLM-generated</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Chapters</span>
<span class="hiw-detail-value">Auto-detected</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Deploy Time</span>
<span class="hiw-detail-value">~2 min</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">12</div>
<div class="hiw-step-content">
<h3>Automated Social Clips</h3>
<p>No manual editing, no scheduling tools. After each episode, an LLM reads the full transcript and picks the best moments — funny exchanges, wild confessions, heated debates. Each clip is automatically extracted, transcribed with word-level timestamps, then polished by a second LLM pass that fixes punctuation, capitalization, and misheard words while preserving timing. The clips are rendered as vertical video with speaker-labeled captions and the show's branding. A third LLM writes platform-specific descriptions and hashtags. Then clips are uploaded directly to YouTube Shorts and Bluesky via their APIs, and pushed to Instagram Reels, Facebook Reels, Mastodon, Nostr, LinkedIn, Threads, and TikTok — nine platforms, zero manual work.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Human Effort</span>
<span class="hiw-detail-value">Zero</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Video Format</span>
<span class="hiw-detail-value">1080x1920 MP4</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Captions</span>
<span class="hiw-detail-value">LLM-polished</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Simultaneous Push</span>
<span class="hiw-detail-value">9 platforms</span>
</div>
</div>
</div>
</div>
<div class="hiw-step">
<div class="hiw-step-number">13</div>
<div class="hiw-step-content">
<h3>Global Distribution</h3>
<p>Episodes are served through a CDN edge network for fast, reliable playback worldwide. The RSS feed is automatically updated and picked up by Spotify, Apple Podcasts, YouTube, and every other podcast app. The website pulls the live feed to show episodes with embedded playback, full transcripts, and chapter navigation — all served through Cloudflare with edge caching. From recording to available on every platform, the whole pipeline is automated end-to-end.</p>
<div class="hiw-detail-grid">
<div class="hiw-detail">
<span class="hiw-detail-label">Audio Delivery</span>
<span class="hiw-detail-value">Global CDN</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Website</span>
<span class="hiw-detail-value">Cloudflare Edge</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Platforms</span>
<span class="hiw-detail-value">5+ directories</span>
</div>
<div class="hiw-detail">
<span class="hiw-detail-label">Feed Format</span>
<span class="hiw-detail-value">RSS + Podcast 2.0</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- What makes it different -->
<section class="hiw-section">
<h2>What Makes This Different</h2>
<div class="hiw-features">
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
</div>
<h3>Not Scripted</h3>
<p>Every conversation is improvised. Luke doesn't know what the caller is going to say. The AI doesn't follow a script. It's a real conversation between a human and an AI character who has a life, opinions, and something on their mind.</p>
</div>
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
</div>
<h3>Built From Scratch</h3>
<p>This isn't an app with a plugin. Every piece — the caller generator, the voice engine, the control room, the phone system, the post-production pipeline, the publishing automation — was built specifically for this show.</p>
</div>
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<h3>Real Time</h3>
<p>Everything happens live. Caller generation, voice synthesis, news lookups, weather checks, phone routing — all in real time during the show. There's no post-production trickery on the caller side. What you hear is what happened.</p>
</div>
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
</div>
<h3>They Listen to Each Other</h3>
<p>Callers aren't isolated — they hear what happened earlier in the show. A caller might disagree with the last guy, back someone up, or call in specifically because of something another caller said. The show builds on itself.</p>
</div>
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
</div>
<h3>Broadcast-Grade Audio</h3>
<p>Every episode runs through a 15-step post-production pipeline: stem limiting, high-pass filtering, de-essing, breath reduction, spoken-word compression, telephone EQ, level matching, music ducking with ad muting, stereo imaging, bus compression, and EBU R128 loudness normalization.</p>
</div>
<div class="hiw-feature">
<div class="hiw-feature-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
</div>
<h3>Fully Automated Pipeline</h3>
<p>From recording to your podcast app, the entire pipeline is automated. Post-production kicks off when the show ends, then a publish script handles transcription, AI-generated metadata, chapter detection, CDN sync, and RSS distribution — all with a single command.</p>
</div>
</div>
</section>
<!-- Post-Production Automation Video -->
<section class="hiw-section">
<h2>Post-Production in Action</h2>
<div class="hiw-hero-card">
<video class="hiw-video" controls playsinline preload="metadata" poster="">
<source src="https://cdn.lukeattheroost.com/videos/reaper-postprod.mp4" type="video/mp4">
</video>
<p class="hiw-video-caption">The entire post-production pipeline runs automatically through Reaper scripting. Silence removal, ad ducking, and EBU R128 loudness normalization — all triggered with a single command when the show ends.</p>
</div>
</section>
<!-- CTA -->
<section class="hiw-section hiw-cta">
<p>Want to hear it for yourself?</p>
<a href="/" class="hiw-cta-btn">Listen to Episodes</a>
<div class="hiw-cta-phone">
Or call in live: <strong>208-439-LUKE</strong>
</div>
<a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener" class="hiw-cta-support">Support the Show</a>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/">Home</a>
<a href="/clips">Clips</a>
<a href="/stats">Stats</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+223 -40
View File
@@ -3,30 +3,69 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Luke at the Roost — The ONLY radio show where Luke gives AI life advice</title>
<meta name="description" content="The ONLY radio show where Luke gives AI life advice. Call in: 208-439-LUKE">
<title>Luke at the Roost — AI Call-In Comedy Podcast</title>
<meta name="description" content="Late-night call-in radio from a desert hermit's RV. Real callers and AI-generated characters talk to Luke about life, love, and everything in between. Call in: 208-439-LUKE.">
<meta name="theme-color" content="#1a1209">
<link rel="canonical" href="https://lukeattheroost.com">
<!-- OG / Social -->
<meta property="og:title" content="Luke at the Roost">
<meta property="og:description" content="The ONLY radio show where Luke gives AI life advice. Call in: 208-439-LUKE">
<meta property="og:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=2">
<meta property="og:site_name" content="Luke at the Roost">
<meta property="og:title" content="Luke at the Roost — AI Call-In Comedy Podcast">
<meta property="og:description" content="Late-night call-in radio from a desert hermit's RV. Real callers and AI characters talk to Luke about life, love, and everything in between.">
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<meta property="og:url" content="https://lukeattheroost.com">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Luke at the Roost">
<meta name="twitter:description" content="The ONLY radio show where Luke gives AI life advice. Call in: 208-439-LUKE">
<meta name="twitter:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=2">
<meta name="twitter:title" content="Luke at the Roost — AI Call-In Comedy Podcast">
<meta name="twitter:description" content="Late-night call-in radio from a desert hermit's RV. Real callers and AI characters talk to Luke about life, love, and everything in between.">
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 4c-2 0-4 2-4 5 0 1 .3 2 .8 3C26 13 24 16 24 20c0 2 .5 4 1.5 5.5L22 28c-2 1-4 3-5 6l-3 10c-.5 2 .5 3 2 3h4l1-4 2 4h6l-1-6 3 6h6l-1-6 3 6h4c1.5 0 2.5-1 2-3l-3-10c-1-3-3-5-5-6l-3.5-2.5C35.5 24 36 22 36 20c0-4-2-7-4.8-8 .5-1 .8-2 .8-3 0-3-2-5-4-5z' fill='%23e8791d'/%3E%3Ccircle cx='30' cy='17' r='1.5' fill='%231a1209'/%3E%3Cpath d='M36 15c1-1 3-1 4 0s1 3 0 4' fill='none' stroke='%23cc2222' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M28 22c2 1 4 1 6 0' fill='none' stroke='%23e8791d' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E">
<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">
<link rel="alternate" type="application/rss+xml" title="Luke at the Roost RSS Feed" href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml">
<link rel="stylesheet" href="css/style.css?v=3">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "PodcastSeries",
"name": "Luke at the Roost",
"description": "The call-in talk show where Luke gives life advice to biologically questionable organisms. Broadcast from a desert hermit's RV, featuring a mix of real callers and AI-generated callers.",
"url": "https://lukeattheroost.com",
"image": "https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3",
"author": {
"@type": "Person",
"name": "Luke MacNeil"
},
"publisher": {
"@type": "Organization",
"name": "MacNeil Media Group"
},
"genre": "Comedy",
"inLanguage": "en",
"webFeed": "https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml",
"sameAs": [
"https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF",
"https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848",
"https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-",
"https://discord.gg/5CnQZxDM"
]
}
</script>
</head>
<body>
<!-- Banner -->
<div class="banner">
<img src="images/banner.png" alt="Luke at the Roost — ON AIR" class="banner-img">
<img src="images/banner.png" alt="Luke at the Roost — ON AIR" class="banner-img" width="1500" height="500">
</div>
<!-- Hero -->
@@ -34,39 +73,74 @@
<div class="hero-inner">
<img
class="cover-art"
src="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=2"
src="images/cover.png"
alt="Luke at the Roost cover art"
width="1440"
height="1440"
>
<div class="hero-info">
<h1>Luke at the Roost</h1>
<p class="tagline">The ONLY radio show where Luke gives AI life advice.</p>
<div class="phone">
<span class="phone-label">Call in live</span>
<span class="phone-number">208-439-LUKE</span>
<span class="phone-digits">(208-439-5853)</span>
<p class="tagline">The call-in talk show where Luke gives life advice to biologically questionable organisms.</p>
<div class="phone" id="phone-section">
<div class="on-air-badge" id="on-air-badge">
<span class="on-air-dot"></span>
ON AIR
</div>
<div class="off-air-badge" id="off-air-badge">
OFF AIR
</div>
<span class="phone-inline">Call in: <strong>208-439-LUKE</strong></span>
</div>
<div class="subscribe-row">
<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>
Spotify
</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">
<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
</a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="subscribe-btn btn-rss">
<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
</a>
<span class="subscribe-label">Listen On</span>
<div class="subscribe-buttons">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="subscribe-btn">
<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
</a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="subscribe-btn">
<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
</a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="subscribe-btn">
<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://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="subscribe-btn">
<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
</a>
</div>
</div>
<div class="secondary-links">
<a href="/how-it-works" class="secondary-link">How It Works</a>
<span class="secondary-sep">&middot;</span>
<a href="/clips" class="secondary-link">Clips</a>
<span class="secondary-sep">&middot;</span>
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="secondary-link">Discord</a>
<span class="secondary-sep">&middot;</span>
<a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener" class="secondary-link support-link">Support the Show</a>
</div>
</div>
</div>
</section>
<!-- About -->
<section class="about-section">
<p>Late-night call-in radio from a desert hermit's RV. Callers ring in with relationship disasters, workplace chaos, and life's dumbest decisions — and Luke tries to help. Sometimes it works. Sometimes it makes things worse.</p>
<p class="about-teaser">Part human callers, part AI-generated characters, fully unhinged advice.</p>
<p><a href="/how-it-works">See how it works</a></p>
</section>
<!-- Featured Clips -->
<section class="home-clips-section">
<div class="home-clips-header">
<h2>Best Clips</h2>
<a href="/clips" class="clips-see-all">See all clips &rarr;</a>
</div>
<div class="home-clips-grid" id="home-clips"></div>
</section>
<!-- Episodes -->
<section class="episodes-section">
<h2>Episodes</h2>
@@ -75,15 +149,122 @@
</div>
</section>
<!-- Testimonials -->
<section class="testimonials-section">
<h2>What Callers Are Saying</h2>
<div class="testimonials-slider" id="testimonials-slider">
<div class="testimonials-track" id="testimonials-track">
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"I called in at 2 AM about my neighbor's rooster situation and Luke talked me off the ledge. Literally saved my relationship with the entire block. My wife thinks I'm crazy for calling a radio show but hey, it worked."</p>
<div class="testimonial-author">
<span class="testimonial-name">Tony M.</span>
<span class="testimonial-location">Lordsburg, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"Called to talk about the Severance finale and ended up getting life advice I didn't know I needed. Luke somehow connected Lumon Industries to my actual job and I quit the next week. Best decision I ever made."</p>
<div class="testimonial-author">
<span class="testimonial-name">Carmen R.</span>
<span class="testimonial-location">Deming, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"I've been listening since episode one. Called in about my truck breaking down outside Animas and Luke spent twenty minutes just talking me through it. Turns out it was the alternator AND my attitude. He was right about both."</p>
<div class="testimonial-author">
<span class="testimonial-name">Dale W.</span>
<span class="testimonial-location">Animas, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9734;</div>
<p class="testimonial-text">"I called in to ask about astrophotography tips and somehow ended up telling Luke about my divorce. He's got this way of getting you to open up. Still shooting the Milky Way every clear night though. Thanks Luke."</p>
<div class="testimonial-author">
<span class="testimonial-name">Jessie K.</span>
<span class="testimonial-location">Silver City, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"My buddy dared me to call in and I ended up having the most real conversation I've had in years. We talked about The Wire for like ten minutes and then he hit me with some truth about why I keep ghosting people. This show is something else."</p>
<div class="testimonial-author">
<span class="testimonial-name">Marcus T.</span>
<span class="testimonial-location">Las Cruces, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"I work night shifts at the mine and this show keeps me sane. Finally called in about a thing with my sister and Luke gave me advice that actually made sense. We're talking again for the first time in three years."</p>
<div class="testimonial-author">
<span class="testimonial-name">Ray D.</span>
<span class="testimonial-location">Tyrone, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9733;</div>
<p class="testimonial-text">"Called about my poker game falling apart because my best friend cheated. Luke compared it to a Breaking Bad episode and somehow made me see the whole situation differently. We play again every Thursday now."</p>
<div class="testimonial-author">
<span class="testimonial-name">Elena S.</span>
<span class="testimonial-location">Hachita, NM</span>
</div>
</div></div>
<div class="testimonial-card"><div class="testimonial-inner">
<div class="testimonial-stars">&#9733;&#9733;&#9733;&#9733;&#9734;</div>
<p class="testimonial-text">"I was just gonna ask about quantum entanglement because I read this article, but Luke turned it into a metaphor for my long distance relationship and honestly? He wasn't wrong. We're moving in together next month."</p>
<div class="testimonial-author">
<span class="testimonial-name">Priya N.</span>
<span class="testimonial-location">Tucson, AZ</span>
</div>
</div></div>
</div>
</div>
<div class="testimonials-dots" id="testimonials-dots"></div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-links">
<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 class="footer-nav">
<a href="/how-it-works">How It Works</a>
<a href="/clips">Clips</a>
<a href="/stats">Stats</a>
</div>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
<p>&copy; 2026 Luke at the Roost</p>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
<!-- Sticky Audio Player -->
@@ -107,6 +288,8 @@
<audio id="audio-element" preload="none"></audio>
<script src="js/app.js"></script>
<script src="js/clips.js"></script>
<script>renderFeaturedClipsInline('home-clips');</script>
<script src="js/app.js?v=2"></script>
</body>
</html>
+98
View File
@@ -119,6 +119,7 @@ function renderEpisodes(episodes) {
const durStr = parseDuration(ep.duration);
const metaParts = [epLabel, dateStr, durStr].filter(Boolean).join(' &middot; ');
const epSlug = ep.link ? ep.link.split('/episodes/').pop()?.replace(/\/$/, '') : '';
card.innerHTML = `
<button class="episode-play-btn" aria-label="Play ${ep.title}" data-url="${ep.audioUrl}" data-title="${ep.title.replace(/"/g, '&quot;')}">
@@ -128,6 +129,7 @@ function renderEpisodes(episodes) {
<div class="episode-meta">${metaParts}</div>
<div class="episode-title">${ep.title}</div>
<div class="episode-desc">${truncate(ep.description, 150)}</div>
${epSlug ? `<a href="/episode.html?slug=${epSlug}" class="episode-transcript-link">Read Transcript</a>` : ''}
</div>
`;
@@ -223,5 +225,101 @@ playerProgress.addEventListener('click', (e) => {
}
});
// Testimonials Slider
function initTestimonials() {
const track = document.getElementById('testimonials-track');
const dotsContainer = document.getElementById('testimonials-dots');
const cards = track.querySelectorAll('.testimonial-card');
if (!cards.length) return;
let currentIndex = 0;
let autoplayTimer = null;
const maxIndex = () => Math.max(0, cards.length - 1);
function buildDots() {
dotsContainer.innerHTML = '';
for (let i = 0; i < cards.length; i++) {
const dot = document.createElement('button');
dot.className = 'testimonial-dot' + (i === currentIndex ? ' active' : '');
dot.setAttribute('aria-label', `Testimonial ${i + 1}`);
dot.addEventListener('click', () => goTo(i));
dotsContainer.appendChild(dot);
}
}
function updatePosition() {
const cardWidth = cards[0].offsetWidth;
track.style.transform = `translateX(-${currentIndex * cardWidth}px)`;
dotsContainer.querySelectorAll('.testimonial-dot').forEach((d, i) => {
d.classList.toggle('active', i === currentIndex);
});
}
function goTo(index) {
currentIndex = Math.max(0, Math.min(index, maxIndex()));
updatePosition();
resetAutoplay();
}
function next() {
goTo(currentIndex >= maxIndex() ? 0 : currentIndex + 1);
}
function resetAutoplay() {
clearInterval(autoplayTimer);
autoplayTimer = setInterval(next, 10000);
}
// Touch/swipe support
let touchStartX = 0;
let touchDelta = 0;
track.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
touchDelta = 0;
clearInterval(autoplayTimer);
}, { passive: true });
track.addEventListener('touchmove', (e) => {
touchDelta = e.touches[0].clientX - touchStartX;
}, { passive: true });
track.addEventListener('touchend', () => {
if (Math.abs(touchDelta) > 50) {
touchDelta < 0 ? goTo(currentIndex + 1) : goTo(currentIndex - 1);
}
resetAutoplay();
});
// Recalculate on resize
window.addEventListener('resize', () => {
if (currentIndex > maxIndex()) currentIndex = maxIndex();
buildDots();
updatePosition();
});
buildDots();
updatePosition();
resetAutoplay();
}
// On-Air Status
function checkOnAir() {
fetch(`https://cdn.lukeattheroost.com/status.json?_=${Date.now()}`)
.then(r => r.json())
.then(data => {
const badge = document.getElementById('on-air-badge');
const offBadge = document.getElementById('off-air-badge');
const phone = document.getElementById('phone-section');
const live = !!data.on_air;
if (badge) badge.classList.toggle('visible', live);
if (offBadge) offBadge.classList.toggle('hidden', live);
if (phone) phone.classList.toggle('live', live);
})
.catch(() => {});
}
// Init
fetchEpisodes();
initTestimonials();
checkOnAir();
setInterval(checkOnAir, 15000);
+85
View File
@@ -0,0 +1,85 @@
const CLIPS_JSON_URL = '/data/clips.json';
const clipPlaySVG = '<svg viewBox="0 0 24 24" fill="#fff"><path d="M8 5v14l11-7z"/></svg>';
function renderClipCard(clip, featured) {
const card = document.createElement('div');
card.className = 'clip-card' + (featured ? ' clip-card-featured' : '');
if (clip.youtube_id) card.dataset.youtubeId = clip.youtube_id;
const hasVideo = !!clip.youtube_id;
const epLabel = clip.episode_number ? `Episode ${clip.episode_number}` : '';
const thumbStyle = clip.thumbnail
? `style="background-image: url('/${clip.thumbnail}'); background-size: cover; background-position: center;"`
: '';
card.innerHTML = `
<div class="clip-card-inner" ${thumbStyle}>
<div class="clip-card-overlay">
<span class="clip-episode-label">${epLabel}</span>
<h3 class="clip-card-title">${clip.title || ''}</h3>
<p class="clip-card-desc">${clip.description || ''}</p>
${hasVideo ? `<button class="clip-play-btn" aria-label="Play clip">${clipPlaySVG}</button>` : ''}
</div>
</div>
`;
if (hasVideo) {
card.querySelector('.clip-play-btn').addEventListener('click', (e) => {
e.stopPropagation();
const inner = card.querySelector('.clip-card-inner');
inner.innerHTML = `<iframe src="https://www.youtube-nocookie.com/embed/${clip.youtube_id}?autoplay=1&rel=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>`;
});
}
return card;
}
async function fetchClips() {
try {
const res = await fetch(CLIPS_JSON_URL);
if (!res.ok) throw new Error('Failed to fetch clips');
return await res.json();
} catch (err) {
console.error('Error loading clips:', err);
return [];
}
}
async function initClipsPage() {
const clips = await fetchClips();
if (!clips.length) return;
const featuredContainer = document.querySelector('.clips-featured');
const gridContainer = document.querySelector('.clips-grid');
if (featuredContainer) {
clips.filter(c => c.featured).forEach(clip => {
featuredContainer.appendChild(renderClipCard(clip, true));
});
}
if (gridContainer) {
clips.forEach(clip => {
gridContainer.appendChild(renderClipCard(clip, false));
});
}
}
async function renderFeaturedClipsInline(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
const clips = await fetchClips();
const featured = clips.filter(c => c.featured);
featured.forEach(clip => {
container.appendChild(renderClipCard(clip, true));
});
}
// Auto-init if clips page containers exist
if (document.querySelector('.clips-featured') || document.querySelector('.clips-grid')) {
initClipsPage();
}
+22
View File
@@ -0,0 +1,22 @@
# Luke at the Roost
> The ONLY radio show where Luke gives AI life advice.
Luke at the Roost is a late-night call-in radio show hosted by Luke MacNeil, broadcast from a desert hermit's RV. The show features a mix of real callers and AI-generated callers who talk to Luke about life, love, relationships, work, and everything in between. It blends comedy, real talk, and empathy with the unique premise of AI characters calling alongside real people.
## Call In
- Phone: 208-439-LUKE (208-439-5853)
- Call during live shows for your chance to talk to Luke
## Listen
- Website: https://lukeattheroost.com
- Spotify: https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF
- Apple Podcasts: https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848
- YouTube: https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-
- RSS: https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml
## About
- Host: Luke MacNeil
- Genre: Comedy
- Format: Live call-in radio show
- Contact: luke@lukeattheroost.com
+130
View File
@@ -0,0 +1,130 @@
<!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=3">
</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@lukeattheroost.com" style="color: var(--accent, #d4a44a);">luke@lukeattheroost.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@lukeattheroost.com" style="color: var(--accent, #d4a44a);">luke@lukeattheroost.com</a></p>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/">Home</a>
<a href="/stats">Stats</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://lukeattheroost.com/sitemap.xml
+249
View File
@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://lukeattheroost.com</loc>
<lastmod>2026-02-15</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://lukeattheroost.com/how-it-works</loc>
<lastmod>2026-03-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://lukeattheroost.com/clips</loc>
<lastmod>2026-03-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://lukeattheroost.com/terms</loc>
<lastmod>2026-02-25</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://lukeattheroost.com/stats</loc>
<lastmod>2026-02-15</lastmod>
<changefreq>daily</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://lukeattheroost.com/privacy</loc>
<lastmod>2026-02-15</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-10-unexpected-windfalls-and-neighborhood-woes</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-9-spilled-juice-and-ghostly-visions</loc>
<lastmod>2026-02-11</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-8-real-news-or-fake-news</loc>
<lastmod>2026-02-11</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-7-ai-takeover-and-honey-endurance</loc>
<lastmod>2026-02-10</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-6-late-night-woes-and-cosmic-contemplations</loc>
<lastmod>2026-02-09</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-5-cosmic-theories-and-calling-for-change</loc>
<lastmod>2026-02-08</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-4-navigating-life-s-challenges</loc>
<lastmod>2026-02-07</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-3-desire-burnout-and-friendship-woes</loc>
<lastmod>2026-02-06</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-2-late-night-confessions</loc>
<lastmod>2026-02-05</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=quantum-physics-pluto-relationship-blunders</loc>
<lastmod>2026-02-04</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-11-quantum-mechanics-and-the-fate-of-tv</loc>
<lastmod>2026-02-13</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</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>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-13-navigating-life-s-unexpected-turns</loc>
<lastmod>2026-02-16</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-14-lies-grief-and-the-pursuit-of-orange</loc>
<lastmod>2026-02-17</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-15-fake-fat-tuesday-trivia-and-life-changing-calls</loc>
<lastmod>2026-02-18</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-16-tasting-the-apocalypse</loc>
<lastmod>2026-02-19</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-17-quantum-chaos-and-cosmic-dust</loc>
<lastmod>2026-02-20</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-18-whistleblowers-warrants-and-wistful-reunions</loc>
<lastmod>2026-02-22</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-19-quantum-regrets-and-relationship-realities</loc>
<lastmod>2026-02-22</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-20-dads-drummers-and-dilemmas</loc>
<lastmod>2026-02-24</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-21-carts-confetti-and-canine-collateral</loc>
<lastmod>2026-02-25</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-22-the-poison-is-the-thing-we-need</loc>
<lastmod>2026-02-25</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-23-secrets-lies-and-revelations</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-24-dealing-with-difficult-dynamics</loc>
<lastmod>2026-02-27</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-25-ghosts-grudges-and-groceries</loc>
<lastmod>2026-03-02</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-26-digital-nomads-deep-fakes-and-workplace-drama</loc>
<lastmod>2026-03-03</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-27-love-loss-and-loud-opinions</loc>
<lastmod>2026-03-04</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-28-secrets-lies-and-vinyl-records</loc>
<lastmod>2026-03-05</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-29-advice-drama-and-life-s-little-complications</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-30-boss-battles-freelance-fears-and-finding-your-way</loc>
<lastmod>2026-03-07</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-31-boundaries-breakups-and-borrowed-books</loc>
<lastmod>2026-03-08</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-32-tacos-taxes-and-tall-tales</loc>
<lastmod>2026-03-09</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-32-tacos-taxes-and-tense-conversations</loc>
<lastmod>2026-03-09</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-33-late-night-confessions-and-cosmic-comedies</loc>
<lastmod>2026-03-11</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://lukeattheroost.com/episode.html?slug=episode-34-hidden-rooms-potlucks-and-mysterious-notes</loc>
<lastmod>2026-03-12</lastmod>
<changefreq>never</changefreq>
<priority>0.7</priority>
</url>
</urlset>
+231
View File
@@ -0,0 +1,231 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Podcast Stats & Downloads — Luke at the Roost</title>
<meta name="description" content="Podcast stats for Luke at the Roost — total downloads, unique listeners, Apple Podcasts reviews, YouTube views and subscribers, updated daily.">
<meta name="theme-color" content="#1a1209">
<link rel="canonical" href="https://lukeattheroost.com/stats">
<meta property="og:site_name" content="Luke at the Roost">
<meta property="og:title" content="Podcast Stats & Downloads — Luke at the Roost">
<meta property="og:description" content="Podcast stats for Luke at the Roost — total downloads, unique listeners, Apple Podcasts reviews, YouTube views and subscribers, updated daily.">
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
<meta property="og:url" content="https://lukeattheroost.com/stats">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Podcast Stats & Downloads — Luke at the Roost">
<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">
<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="alternate" type="application/rss+xml" title="Luke at the Roost RSS Feed" href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml">
<link rel="stylesheet" href="css/style.css?v=3">
</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>Stats</h1>
<p class="page-subtitle">Downloads, reviews, and audience numbers across all platforms.</p>
<p class="stats-updated" id="stats-updated"></p>
</section>
<!-- Stats Content -->
<div class="stats-container" id="stats-container">
<div class="stats-loading" id="stats-loading">Loading stats...</div>
</div>
<noscript>
<div class="stats-container">
<p>This page requires JavaScript to display live stats. Luke at the Roost is available on <a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF">Spotify</a>, <a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848">Apple Podcasts</a>, <a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-">YouTube</a>, and all major podcast apps.</p>
</div>
</noscript>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/">Home</a>
<a href="/how-it-works">How It Works</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
<script>
(async function() {
const container = document.getElementById('stats-container');
const loading = document.getElementById('stats-loading');
const updatedEl = document.getElementById('stats-updated');
try {
const resp = await fetch('https://cdn.lukeattheroost.com/stats.json');
if (!resp.ok) throw new Error('Failed to load stats');
const data = await resp.json();
if (data.updated_at) {
const d = new Date(data.updated_at);
updatedEl.textContent = 'Last updated ' + d.toLocaleDateString('en-US', {
month: 'long', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: '2-digit', timeZoneName: 'short'
});
}
let html = '';
// Downloads (Castopod)
if (data.castopod) {
const c = data.castopod;
html += '<section class="stats-section">';
html += '<h2>Downloads</h2>';
html += '<div class="stats-summary">';
html += '<div class="stat-big"><span class="stat-number">' + (c.total_downloads || 0).toLocaleString() + '</span><span class="stat-label">Total Downloads</span></div>';
html += '<div class="stat-big"><span class="stat-number">' + (c.unique_listeners || 0).toLocaleString() + '</span><span class="stat-label">Unique Listeners</span></div>';
html += '</div>';
if (c.episodes && c.episodes.length) {
html += '<div class="stats-list">';
c.episodes.forEach(function(ep) {
html += '<div class="stats-list-item">';
html += '<span class="stats-list-title">' + escapeHtml(ep.title) + '</span>';
html += '<span class="stats-list-meta">' + (ep.downloads || 0).toLocaleString() + ' downloads &middot; ' + escapeHtml(ep.date) + '</span>';
html += '</div>';
});
html += '</div>';
}
html += '</section>';
}
// Apple Reviews
if (data.apple) {
const a = data.apple;
html += '<section class="stats-section">';
html += '<h2>Apple Podcasts</h2>';
if (a.review_count > 0) {
html += '<div class="stats-summary">';
html += '<div class="stat-big"><span class="stat-number">' + (a.avg_rating || 0) + '/5</span><span class="stat-label">Average Rating</span></div>';
html += '<div class="stat-big"><span class="stat-number">' + a.review_count + '</span><span class="stat-label">Reviews</span></div>';
html += '</div>';
if (a.reviews && a.reviews.length) {
html += '<div class="stats-reviews">';
a.reviews.forEach(function(r) {
const stars = '\u2605'.repeat(r.rating) + '\u2606'.repeat(5 - r.rating);
html += '<div class="review-card">';
html += '<div class="review-stars">' + stars + '</div>';
html += '<div class="review-title">' + escapeHtml(r.title) + '</div>';
if (r.content && r.content !== r.title) {
html += '<div class="review-content">' + escapeHtml(r.content) + '</div>';
}
html += '<div class="review-meta">' + escapeHtml(r.author) + ' &middot; ' + escapeHtml(r.date) + ' &middot; ' + escapeHtml(r.storefront) + '</div>';
html += '</div>';
});
html += '</div>';
}
} else {
html += '<p class="stats-empty">No reviews yet</p>';
}
html += '</section>';
}
// Spotify
if (data.spotify) {
const s = data.spotify;
html += '<section class="stats-section">';
html += '<h2>Spotify</h2>';
html += '<div class="stats-summary">';
if (s.rating) {
html += '<div class="stat-big"><span class="stat-number">' + s.rating + '/5</span><span class="stat-label">Rating</span></div>';
}
html += '</div>';
if (s.url) {
html += '<p class="stats-link"><a href="' + escapeHtml(s.url) + '" target="_blank" rel="noopener">' + (s.rating ? 'Listen on Spotify' : 'Rate us on Spotify') + '</a></p>';
}
html += '</section>';
}
// YouTube
if (data.youtube) {
const y = data.youtube;
html += '<section class="stats-section">';
html += '<h2>YouTube</h2>';
html += '<div class="stats-summary">';
html += '<div class="stat-big"><span class="stat-number">' + (y.total_views || 0).toLocaleString() + '</span><span class="stat-label">Views</span></div>';
html += '<div class="stat-big"><span class="stat-number">' + (y.total_likes || 0).toLocaleString() + '</span><span class="stat-label">Likes</span></div>';
html += '<div class="stat-big"><span class="stat-number">' + (y.total_comments || 0).toLocaleString() + '</span><span class="stat-label">Comments</span></div>';
if (y.subscribers != null) {
html += '<div class="stat-big"><span class="stat-number">' + y.subscribers.toLocaleString() + '</span><span class="stat-label">Subscribers</span></div>';
}
html += '</div>';
if (y.videos && y.videos.length) {
html += '<div class="stats-list">';
y.videos.forEach(function(v) {
html += '<div class="stats-list-item">';
html += '<span class="stats-list-title">' + escapeHtml(v.title) + '</span>';
html += '<span class="stats-list-meta">' + (v.views || 0).toLocaleString() + ' views &middot; ' + (v.likes || 0).toLocaleString() + ' likes &middot; ' + escapeHtml(v.date) + '</span>';
html += '</div>';
});
html += '</div>';
}
html += '</section>';
}
loading.style.display = 'none';
container.innerHTML = html;
} catch (e) {
loading.textContent = 'Unable to load stats. Try again later.';
loading.className = 'stats-error';
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
})();
</script>
</body>
</html>
+132
View File
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Terms of Service — Luke at the Roost</title>
<meta name="description" content="Terms of service for Luke at the Roost podcast and website.">
<meta name="theme-color" content="#1a1209">
<link rel="canonical" href="https://lukeattheroost.com/terms">
<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=3">
</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>Terms of Service</h1>
<p class="page-subtitle">Last updated: February 25, 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>Agreement to Terms</h2>
<p>By accessing or using the Luke at the Roost website (<strong>lukeattheroost.com</strong>), podcast, or any related services (collectively, the "Service"), you agree to be bound by these Terms of Service. If you do not agree, do not use the Service.</p>
<h2>Description of Service</h2>
<p>Luke at the Roost is an entertainment podcast featuring AI-generated characters and caller interactions. The Service includes the website, podcast episodes, social media accounts, and any associated applications or integrations operated by MacNeil Media Group.</p>
<h2>Content</h2>
<h3>Our Content</h3>
<p>All podcast episodes, transcripts, artwork, and website content are owned by MacNeil Media Group. You may share links to episodes and embed our public content for non-commercial purposes with attribution. You may not redistribute, modify, or use our content for commercial purposes without written permission.</p>
<h3>User-Submitted Content</h3>
<p>By calling in to the show, submitting voicemails, or otherwise contributing content, you grant MacNeil Media Group a perpetual, worldwide, royalty-free license to use, reproduce, modify, and distribute your contribution in connection with the Service. You represent that you have the right to grant this license.</p>
<h3>Explicit Content</h3>
<p>The podcast is rated explicit and may contain strong language, adult themes, and mature content. It is intended for audiences aged 18 and older.</p>
<h2>Acceptable Use</h2>
<p>You agree not to:</p>
<ul style="margin: 0.5em 0 1em 1.5em;">
<li>Use the Service for any unlawful purpose</li>
<li>Attempt to interfere with or disrupt the Service</li>
<li>Scrape, crawl, or collect data from the Service in an automated manner without permission</li>
<li>Impersonate the show, its host, or its characters</li>
<li>Upload or transmit malicious code or content</li>
</ul>
<h2>Third-Party Services</h2>
<p>The Service integrates with third-party platforms including Spotify, Apple Podcasts, YouTube, TikTok, Facebook, Instagram, Discord, and others. Your use of these platforms is governed by their respective terms of service. We are not responsible for the practices or content of third-party services.</p>
<h2>Disclaimer of Warranties</h2>
<p>The Service is provided "as is" and "as available" without warranties of any kind, express or implied. We do not guarantee that the Service will be uninterrupted, error-free, or free of harmful components. All characters and scenarios in the podcast are fictional and for entertainment purposes only.</p>
<h2>Limitation of Liability</h2>
<p>To the maximum extent permitted by law, MacNeil Media Group shall not be liable for any indirect, incidental, special, consequential, or punitive damages arising from your use of or inability to use the Service.</p>
<h2>Changes to Terms</h2>
<p>We may update these Terms from time to time. Changes will be posted on this page with an updated date. Continued use of the Service after changes constitutes acceptance of the new Terms.</p>
<h2>Termination</h2>
<p>We reserve the right to suspend or terminate access to the Service at any time, for any reason, without notice.</p>
<h2>Governing Law</h2>
<p>These Terms are governed by the laws of the State of Idaho, United States, without regard to conflict of law principles.</p>
<h2>Contact</h2>
<p>MacNeil Media Group<br>
Email: <a href="mailto:luke@lukeattheroost.com" style="color: var(--accent, #d4a44a);">luke@lukeattheroost.com</a></p>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-nav">
<a href="/">Home</a>
<a href="/stats">Stats</a>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Listen On</span>
<div class="footer-icons-row">
<a href="https://open.spotify.com/show/0ZrpMigG1fo0CCN7F4YmuF?si=f990713adce84ba4" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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></a>
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener" class="footer-icon-link" aria-label="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.814z"/><path d="M9.545 15.568V8.432L15.818 12z" fill="#1a1209"/></svg></a>
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Apple Podcasts"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 3.293 1.592 6.214 4.05 8.04.13-.455.283-.942.457-1.393A9 9 0 0 1 3 12a9 9 0 0 1 18 0 9 9 0 0 1-3.507 7.127c.174.42.327.893.456 1.333A10 10 0 0 0 22 12c0-5.523-4.477-10-10-10zm0 4a6 6 0 0 0-6 6c0 1.87.856 3.54 2.2 4.64.196-.46.43-.91.692-1.31A4.5 4.5 0 0 1 7.5 12a4.5 4.5 0 0 1 9 0c0 1.21-.478 2.31-1.256 3.12.24.37.462.8.655 1.24A6 6 0 0 0 18 12a6 6 0 0 0-6-6zm0 4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM12 15c-.75 0-1.158.54-1.28 1.2-.17.94-.28 1.91-.33 2.88-.03.48.34.82.73.82h1.76c.39 0 .76-.34.73-.82-.05-.97-.16-1.94-.33-2.88-.122-.66-.53-1.2-1.28-1.2z"/></svg></a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener" class="footer-icon-link" aria-label="RSS"><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></a>
</div>
</div>
<div class="footer-icons">
<span class="footer-icons-label">Follow</span>
<div class="footer-icons-row">
<a href="https://discord.gg/5CnQZxDM" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Discord"><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-.028 14.09 14.09 0 0 0 1.226-1.994.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.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg></a>
<a href="https://www.facebook.com/profile.php?id=61588191627949" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Facebook"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a>
<a href="https://www.instagram.com/lukeattheroost/" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Instagram"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg></a>
<a href="https://x.com/lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="X"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a>
<a href="https://bsky.app/profile/lukeattheroost.bsky.social" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Bluesky"><svg viewBox="0 0 568 501" fill="currentColor"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C10.945 203.659 1 75.291 1 57.946 1-28.906 76.134-1.612 123.121 33.664z"/></svg></a>
<a href="https://mastodon.macneilmediagroup.com/@lukeattheroost" target="_blank" rel="me noopener" class="footer-icon-link" aria-label="Mastodon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054 19.648 19.648 0 0 0 4.636.528c.164 0 .329 0 .494-.002 1.694-.042 3.48-.152 5.12-.554 2.21-.543 4.137-2.186 4.348-4.55.162-1.808.21-3.627.142-5.43-.02-.6-.168-1.874-.168-1.874z"/><path d="M19.903 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192zM9.337 7.515v5.834c0 1.226-.996 2.222-2.222 2.222h-.796c-1.226 0-2.222-.996-2.222-2.222V7.628c0-1.226.996-2.222 2.222-2.222h.796c.122 0 .242.01.36.03 1.076.164 1.862 1.098 1.862 2.192z" fill="#1a1209"/></svg></a>
<a href="https://primal.net/p/nprofile1qqswsam9cx06j7sxzpl498uquk3kgrwedxtq48j57zxkuj8fs82xtugge0wtg" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Nostr"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186.31a.27.27 0 0 0-.372 0C8.46 3.487 2.666 9.93 2.666 15.042c0 5.176 4.183 8.958 9.334 8.958s9.334-3.782 9.334-8.958c0-5.112-5.794-11.555-9.148-14.732z"/></svg></a>
<a href="https://www.threads.com/@lukeattheroost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="Threads"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.781 3.632 2.695 6.54 2.717 2.227-.017 4.048-.59 5.413-1.703 1.428-1.163 2.076-2.645 1.925-4.403-.098-1.13-.578-2.065-1.39-2.7-.811-.636-1.905-.993-3.164-1.033a11.253 11.253 0 0 0-.04 0c-1.078.007-2.044.289-2.79.816-.68.481-1.069 1.108-1.125 1.813-.057.72.264 1.32.877 1.64.554.29 1.317.437 2.271.437l.013-.001c.652-.004 1.383-.078 2.172-.218l.386 2.022c-.947.18-1.837.273-2.643.278a10.35 10.35 0 0 1-.143 0c-1.425-.013-2.657-.284-3.66-.804-1.237-.643-1.928-1.745-1.836-2.93.099-1.258.738-2.316 1.849-3.064 1.088-.732 2.466-1.12 3.988-1.124h.05c1.644.044 3.088.528 4.178 1.398 1.133.905 1.8 2.185 1.935 3.703.2 2.258-.697 4.2-2.598 5.75-1.668 1.36-3.863 2.087-6.348 2.105z"/></svg></a>
<a href="https://www.linkedin.com/company/luke-at-the-roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
<a href="https://www.tiktok.com/@luke.at.the.roost" target="_blank" rel="noopener" class="footer-icon-link" aria-label="TikTok"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg></a>
</div>
</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"><a href="https://ko-fi.com/lukemacneil" target="_blank" rel="noopener">Support the Show</a></p>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@lukeattheroost.com">luke@lukeattheroost.com</a></p>
<p>&copy; 2026 Luke at the Roost &middot; <a href="/privacy">Privacy Policy</a> &middot; <a href="/terms">Terms of Service</a> &middot; <a href="https://monitoring.macneilmediagroup.com/status/lukeattheroost" target="_blank" rel="noopener">System Status</a></p>
</footer>
</body>
</html>
@@ -0,0 +1,329 @@
LUKE: Alright, welcome back to Luke at the Roost. This is the Colin radio show where you call in and tell me what's on your mind. My name is Luke, I'm your host. If you'd like to call in, our number is 208-439-5853. That's 208-439-Luke. Let's get the show started and our first caller today is going to be Slim. Slim, welcome to the show. What would you like to talk about today?
SLIM: Hey Luke, yeah thanks for taking my call. So look, I won 50 grand on a scratch ticket on Monday and I haven't told a single person yet. Not my wife, not the church, nobody, and it's eating me alive because I'm a youth pastor, right? Right?
LUKE: Wow, congratulations on your 50 grand win, what are you going to do with the money?
SLIM: Well, that's the whole thing, Luke. I don't know. I mean, part of me wants to just pay off the truck, maybe fix the AC at the church because it's been broken since September and the board keeps saying there's no money.
LUKE: Well, here's a question for you. If you're a pastor and you work at a church and you win the lottery, do you have to pay tax on that lottery winning?
SLIM: Oh, yeah, absolutely. The state already took their cut when I claimed it, walked out with about 37 after taxes.
LUKE: Oh, you've already got the money and you haven't spent any of it yet?
SLIM: Nope, it's just sitting in the bank like a damn time bomb. I keep thinking about how my dad used to say, money's like a deer. You spook it, it's gone. And I'm standing there with a rifle full of buckshot, trying not to move.
LUKE: Wow, that's one of the most confusing analogies I've ever heard in my life. But congratulations to you for your win. I can't imagine it's going to cost you 37 grand to fix the AC in your church.
SLIM: No, you're right. Probably three, four thousand tops, but see, that's where it gets tricky. The second people find out I have this money, it's going to be slim. Can you help with the new himmels or slim? My sister's kid needs braces. I've been doing this job for 12 years, Luke.
LUKE: And what is your lord and savior say to do with the money?
SLIM: Man, that's the question I've been wrestling with all week. I keep going back to the parable of the talents. You know, the guy who buried his money in the ground got reamed for it. But then there's also, don't let your left hand know what your right hand is doing when it comes to giving. I've been praying on it every night this week, honestly.
LUKE: Well, I think you're probably going to come to the right, uh, right decision and do the right thing with the money. Actually, you know what, I don't know if there is a right thing to do with the money. It wasn't there before. You wanted in the lottery. Good for you. Do you play the lottery a lot? How much are you, were you down to win this 50 grand?
SLIM: First ticket I ever bought actually. Yeah, sure. I was at the circle K getting gas and just, I don't know, impulse five bucks. My wife doesn't even know. She thinks gambling's a sin. And honestly, I kind of agreed with her until I scratched that thing off in the parking lot and nearly dropped my coffee.
LUKE: Well, you know, what the man with no sin throws stones first or whatever. Uh, good for you. Good for your congregation. I hope that the money goes to good use. And, um, and yeah, I don't think you have to feel bad about winning. Just do something good with the money and it's all good.
SLIM: Yeah, you're right. I appreciate that, Luke. I think I've been making this harder than it needs to be.
LUKE: For sure. And speaking of money, I got a question for you. How about you tell us about something that you bought once that you regret? Uh, something you spent money on and wished later that you hadn't.
SLIM: Oh, man. That's easy. About eight years ago, I bought this used jet ski off some guy in Lord'sburg. Thought I'd take it out to Elephant Butte. You know, make a whole thing of it with the youth group.
LUKE: You bought a, uh, Lord'sburg jet ski. How did that work out?
SLIM: No, no, Lord'sburg. It's a town about an hour west of here. Oh, yeah. But yeah, the jet ski paid $2,200 bucks for it. Drove it up to the lake exactly one time. Thing broke down in the middle of the water. Had to get towed back to shore by some guy on a pontoon boat.
LUKE: Hey, well, now I guess the good news is you get 37 grand.
SLIM: You know, the thing's been sitting under a tarp behind my garage for seven years. I think my wife would actually divorce me if I put another dime into it. She brings it up every time I want to buy anything. Remember the jet ski slim? It's like my personal scarlet letter back there.
LUKE: All right. Thanks for the call slim and, uh, congratulations on your big lottery win. Uh, just don't take it back to the casino and you should be all right. Next up on the line, we have, uh, Sandy. Sandy, welcome to the show. What's what's on your mind today, Sandy?
SANDY: Hey, Luke, thanks for taking my call. So I got into it with my neighbor tonight, like physically got into it. And I'm sitting here trying to figure out if I need to call the cops or if that's just going to make everything worse. We've been going back and forth for months now, but this time she actually shoved me in my own driveway.
LUKE: shoved you how just to like a little pusher where you actually injured in some way, where you did you fear for your life in this altercation?
SANDY: No, I mean, I didn't fall or anything, but it wasn't like a little tap either. She put both hands on my shoulders and pushed me back hard enough that I stumbled. The thing is I'm honestly more pissed off than scared, you know? But that's what's got me worried because I shoved her right back and now I'm thinking if I call the police, she's going to say I started it. This whole thing kicked off because her dog keeps shitting in my yard and she refuses to pick it up.
LUKE: Okay, so the dog shits in your yard, she's not picking it up and, uh, and how does that turn into an altercation? You obviously confronted her about the dog shitting situation.
SANDY: Yeah, so tonight I'm coming home from a birth. I was up for like 16 hours and I step right in a fresh pile getting out of my car. I lost it, walked straight over to her place and knocked on the door with the bottom of my shoe still covered in it.
LUKE: I should have kicked her in the face.
SANDY: Ha, I mean, I was tempted, but no, I just told her this is the third time this week and she needs to keep her damn dog on her own property.
LUKE: Well, yeah, that's annoying, it's never fun to step in and pile a dog poo. But, uh, dogs do roam around and it is, uh, the outdoors and I understand that people own their yards and stuff and it's, that's not fun, but the same time it's kind of hard to control wild animals from doing wild animal things.
SANDY: See, that's the thing though. This isn't a wild animal. It's her dog. She lets it out in the morning without a leash and it comes straight to my yard every single time.
LUKE: Would it make any difference if it was a wild dog?
SANDY: I mean, yeah, because then I couldn't go knock on someone's door about it, but this is her dog. She's responsible for it and honestly, if she just apologized once or picked it up, we wouldn't even be here. Instead, she tells me I'm being dramatic and that dogs go where they go. That's when my driveway and she followed me.
LUKE: Okay, well, um, that's, that's no fun. It's never good to have a shitty situation with your neighbor. What are you going to do about it?
SANDY: That's why I'm calling you, Luke. I don't know.
LUKE: Well, uh, you could put up a fence, you could move, you could, uh, complain a lot at the town meeting. I don't, you can't really physically assault the woman. You can't call the police on her. I guess you could call animal control if you actually catch the dog in your yard. Are you positive that it's her dog? Have you actually caught the dog in the act?
SANDY: Yeah, I've seen it. Walked outside two mornings ago and watched it squat right by my mailbox while she's standing on her porch drinking coffee. And I did call animal control last month.
LUKE: All right. Thanks for the call, Sandy. Good luck with your, uh, your dookie shoes. And next on the radio show, we've got Keith. Keith, welcome to the show. You're calling back. What's up today, sir?
KEITH: Hey, Luke. Yeah, I'm back. So I got an update on that video situation and I'm not going to lie. It got weird real weird. I ended up taking your advice kind of and I talked to her last week. Turns out she's been coming to the cemetery because her apartment is too quiet. And she likes hearing the highway noise from where I work.
LUKE: See, I told you it wasn't real weird. That's not weird. What's weird about that?
KEITH: Well, okay, that pot's not weird. You're right. But here's the thing. After we talked that first time, she started showing up on my shift specifically. Like she knows my schedule now and two nights ago, she brought me coffee. Just walked right up to the shed with two cups from that circle. K on Hudson and sat down like we were having a planned meeting.
LUKE: Okay, that still doesn't sound very weird to me, sir. Let's get to the weird part.
KEITH: All right. So last night, she asked if I wanted to come over to her place after my shift ends. Just straight up asked me. And I'm sitting there thinking, this woman is maybe 60, 65. And I'm married to Teresa. And I don't even know this lady's actual name. I've just been calling her this whole time.
LUKE: Okay, so you got to let her down a little bit easy. Sorry, I'm married. It's not that weird that a lonely widow is looking for some companionship at the cemetery. I mean, maybe that's a little weird, but it's not that weird.
KEITH: Yeah, I mean, you're right. It's not that weird when you say it like that. But here's where I messed up. I didn't say I was married. I panicked and told her I had to go check on something by the North fence. And I just walked away like a coward again.
LUKE: Okay, well, why is that? Is it because you actually want something to go on with this widow? Is that what's the eating you up?
KEITH: No, man, that's not look. I don't want anything with her. But things with Teresa have been so damn cold lately. We barely talk anymore except about bills or whose turn it is to take the trash out. And here's this woman who's actually interested in talking to me, bringing me coffee, asking about my day.
LUKE: Yeah, I understand that. It's nice to feel wanted and seen and heard and all that. And it's okay for you to have other friends. I mean, you can befriend this woman without doing anything shady at her house behind your wife's back. There's nothing wrong with that.
KEITH: You're right. I know you're right. But I'm not going to lie. Part of me keeps thinking about what it would be like to just say yes. Not even for anything physical. Just to sit in someone's living room and have a conversation where the other person actually wants to hear what I have to say. Teresa used to be like that.
LUKE: Yeah, well, it sounds like you've got a couple things going on and I don't think it's that weird to go over somebody's house and have a cup of coffee and talk if you're seriously not into anything romantic or pursuing that relationship behind your wife's back. You might want to talk to Teresa about that before you start going over a strange woman's house though.
KEITH: Yeah, that's the problem though. How do I even start that conversation? Hey, Teresa, there's this widow at the cemetery who wants to hang out. Is that cool? Yeah. She's already pissed at me half the time about stuff I don't even understand. Last week, she got mad because I didn't notice she rearranged the living room. I walked right past the couch in a different spot three times before she pointed it out.
LUKE: Yeah, well, I mean, that sounds like marriage. So, I mean, you're in it. But I think that's exactly the way that you deal with it. Hey, I met a new friend. I'd like to hang out after work one day, have a cup of coffee and talk about stuff. It's not a romantic thing. I'm not interested. She's 65 years old and I'm married to you. Teresa, the love of my life who I would never have eyes for anyone else. And just make sure she knows that and she's got a problem with it then deal with that problem.
KEITH: I hear you, but man, I don't know if I can say love of my life right now and have it sound real. She'd see right through that.
LUKE: All right, well, if that's the truth then you and your wife need to sit down and have some hard conversations and figure out if you want to continue on living the way you're living because that's that's no good and you might be better off with this widow or, you know, somebody else.
KEITH: Yeah, I've been sitting there thinking about that too. Maybe that's why this whole thing is eating at me so much.
LUKE: Yeah, it could be. I mean, relationships are tough and if things have been downhill for a while and you don't see them moving in a positive direction, it's time to have some hard conversations.
KEITH: The thing is, we used to be good, really good. I don't know when it changed exactly. Somewhere between the second mortgage and her mom getting sick and me taking this graveyard shift, no pun intended. We just sort of stop being on the same team.
LUKE: I think that happens. I think there are ways that you can get back on the same team if you both want to, but you might also find that neither one of you even, you know, likes each other anymore and it's time to split up and that's okay too. I'm not going to lie.
LUKE: Let's see. What's something you believed in 10 years ago that you don't believe anymore?
EARL: Oh man, that's a hell of a shift. 10 years ago I thought the tea party was going to actually change things in Washington.
LUKE: Well, things did change in Washington.
EARL: Yeah, but not the way we thought. I mean, I was all in on that stuff. Smaller government, fiscal responsibility, the whole deal. Now I look at it, and I'm sitting there thinking none of these people actually meant any of it. They just wanted their turn at the trough.
LUKE: Yeah, man, you're living you learn. That seems to be the way. No matter who is in the office. They do not give a fuck about you. All right, thanks for the call. Talk to your wife. Don't be a pussy. Next up on the line, we have Laurie. Laurie, welcome to the show. How can we help you today?
LAURIE: Hey, Luke, thanks for taking my call. So I've been working from home doing data entry for like three years now. And honestly, it can get pretty isolating. But I'm also really into open source technology. And I spend a lot of my free time in these online communities, you know, contributing to projects, helping troubleshoot stuff.
LUKE: I do know. I do a lot of that myself. So what are you working on?
LAURIE: Oh, nice. Right now I'm actually contributing to this documentation project for a Linux distribution. Nothing glamorous, but it's stuff people actually use. And I've been learning rust on the side messing around with some smaller tools. But here's why I called. I was watching the Lost Finale the other day, right?
LUKE: Oh, yeah, I just watched the Lost Finale a couple weeks ago or maybe a couple months ago now. But yeah, what about it?
LAURIE: Okay, so it really got me thinking about community and how people come together around something bigger than themselves. And with Valentine's Day coming up, I'm sitting here alone most days, just me and my computer screen. And I realized, these open source communities, they're the closest thing I have to that feeling of belonging, you know? Like actually being part of something that matters.
LUKE: Yes, I do know I totally understand that. And when I was a kid, I used to hang out in IRC in the on the free node network. And we had a channel called Linux people. And we did tutorials and wrote articles and helped people that were trying to learn Linux. They would pop into IRC and ask their questions. And we all tried our best to help them out. And I still talk to some of those people today. That's been over 20 years. People that I've never met that I met in those communities. So I don't think that's a bad thing at all.
LAURIE: Yeah, exactly. That's what I'm talking about. And honestly, Luke, I think people don't get how real those connections are. Like my family thinks I'm just sitting here alone all the time, but I'm collaborating with people in Germany and Brazil and wherever working on actual things that help people. But I guess what's been eating at me is is that enough?
LUKE: Do you feel like it's enough? I mean, I usually do, sometimes I don't, but most of the time I do feel like it's more than enough. That's a very personal question. How do you feel about it? It's obviously bothering you.
LAURIE: I mean, most of the time, yeah, it feels good. But then like Thursday night rolls around and Valentine's Day is coming up. And I'm 29. And I look around my house and it's just quiet.
LUKE: Quiet's very nice though. And 29's not all that old. You get plenty of time to do whatever you like. So I applaud you for spending your time doing productive stuff with the type of people that make you better at what you do. I think that's a good way to deal with your loneliness and to find some community and camaraderie out in the world.
LAURIE: Thanks, I appreciate that. I guess part of me worries though. Like, am I using these communities as a substitute for something else? Or is this just what connection looks like now?
LUKE: Yeah, there's probably some of both of that. Now connection can be, it can look like a lot of different things. You don't have to be in bed with somebody to have a connection with them. You could, you know, have a very good working relationship and friendship with somebody over the internet in Brazil. That's not all that strange or weird, especially today. I mean, 20 years ago when I was doing it and I was 12 years old, that was a little bit odd. But I don't think it's very strange now.
LAURIE: You're right. And honestly, I heard Sandy call earlier tonight and I felt like she was holding something back about that whole neighbor situation.
LUKE: Yeah, you know what, I think she was too. I certainly got that impression, but she wasn't going to spill it. So we're just going to have to go with her go with what she wanted to divulge. Anyway, Laurie, thanks for the call. Keep it up with your documentation project. And if you want to find somebody to hang out with on Valentine's Day, I'm sure you can do that. You sound like a very nice lady. You enjoy the remainder of your night, all right? Hey, Gus, Gus, welcome to the show. What's happening?
GUS: Yeah. Hey, Luke, so my ex showed up at the pawn shop today with flowers like a whole bouquet and my girlfriend was there picking me up for lunch. She saw the whole thing through the window before she even came in. Now she won't talk to me and I don't know what the hell I'm supposed to say to fix this.
LUKE: Your ex brought you flowers on Valentine's Day at your work?
GUS: Well, not quite Valentine's Day yet. It's Thursday, so we got a few days. But yeah, just showed up out of nowhere. I haven't talked to Melissa in like eight months. And she walks in with these roses talking about how she's been thinking about things. My girlfriend Sarah sees her through the window. And by the time she comes inside, Melissa's already gone, but the flowers are sitting right there on the counter. Sarah just looked at me, turned around and left.
LUKE: Well, which one of them would you rather be with? Melissa or Sarah?
GUS: Sarah? I mean, it's Sarah, no question. Melissa and I were done for good reasons. She couldn't handle that I work weird hours. Always wanted me to be someone I'm not.
LUKE: Yeah, well, then problem solved. Just tell Sarah, hey, I didn't ask her to bring me these flowers. There's nothing going on here. I'm not interested in her anymore. And it's a free country. You know when somebody wants to bring your flowers, then all right, you wasted your 24 bucks.
GUS: Yeah, I tried that. I called her like three times. Center text saying exactly that. I didn't ask for this. I don't want Melissa back. The whole thing.
LUKE: Here's what you do. I got it figured out. So you take the flowers that Melissa gave you and you bring them home. And on Valentine's Day, you give those same flowers to Sarah and say these are for you.
GUS: Oh, man, that's, I mean, Sarah's not stupid. She saw those exact flowers. They were these yellow roses with some kind of purple things mixed in. Real specific looking.
LUKE: Yeah, well, I think she'll appreciate your frugality. There's no use to wasting the money on new flowers. If you already got something, you're not going to use. You might as well get some use out of them. That's what I say.
GUS: I don't know, Luke.
LUKE: I feel like showing up with the same flowers my ex brought me is going to make things about 10 times worse. Sarah's already pissed enough. Last thing I need is her thinking I'm too cheap or too clueless to get her her own flowers.
GUS: Well, I mean, that's what I would do. I would, I would take the flowers that were given to me that I don't want and give them back to her and be like, here, here you go. I was thinking of you.
LUKE: You know what? Maybe that works for you, but I think Sarah would throw them at my head. She's not the type to find that charming. She'd see it as me being either lazy or rubbing salt in the wound. I'm already in the doghouse here. I need to figure out how to actually fix this, not make it worse with recycled roses.
GUS: Well, you didn't do anything wrong. So you really don't have anything to fix. If, uh, if there's jealousy going on there, then that's, that's really on her. You can just say, hey, I let her know. I did not prompt this in any way. There's nothing going on behind your back. I can't control what she does. She came and dropped off flowers and you saw it and that's unfortunate, but there's nothing going on there and you're the one that I want to be with and commit to her in that way. And if she's got a problem with it, then, uh, then you got to, then you got to deal with that and then you might be better off going back to Melissa.
LUKE: I hear you, but it's not just jealousy. Sarah watched my axe walk into my workplace flowers and a whole speech about how she misses me and wants to try again. That's not nothing.
GUS: It is nothing. It's nothing that you did. I mean, you can control that.
LUKE: So yeah, but I didn't shut it down fast enough either. I just kind of stood there like an idiot while Melissa went on and Sarah saw that. I should have cut her off right away, told her to leave.
GUS: Well, it sounds like you're a pussy either way. You can either stand up to your current girlfriend or you can stand up to your former girlfriend, but one way or the other, you're going to have to stand up.
LUKE: You're right. I've been trying to keep everybody happy, and I'm just making it worse. I froze up with Melissa because I didn't want to be a jerk, and now Sarah thinks I was actually considering it or something.
GUS: Just sit her down and have that conversation, and if that's a problem, then it probably shows how long you've been with this chick, Sarah. How long?
LUKE: About eight months, we moved in together maybe two months ago, which I know was probably too fast, but rents expensive, and we were spending every night together anyway.
GUS: Okay, well, you don't know this woman at all. So if something this small is going to cause strife in your life, maybe it's time to find another roommate.
LUKE: That's the thing though. It's not really that small when you think about it. My ex showing up with flowers isn't like some random thing, and Sarah and I were solid before this. She's been good to me, better than Melissa ever was.
GUS: All right, well then talk to her and get it worked out, and if she's a reasonable person, she'll come around. If she's not a reasonable person, then you're an idiot for dealing with that, but that's your choice, and that's my advice, and I'm sticking to it.
LUKE: And now it's time to go to a word from our sponsors. Let's talk about mental health. Specifically, let's talk about brutal honesty, the therapy app that's tired of your shit. Unlike other apps with their calming voices and gentle affirmations, brutal honesty pairs you with an AI therapist that tells you what your friends are too nice to say. Did you try journaling about it? No, we're not doing that. Our algorithm analyzes your patterns and asks questions like, why do you think you deserve to feel better when you don't even drink water? And is your anxiety real or did you just have four cold brews on an empty stomach? Brutal honesty because coddling yourself got you here. First session is free, but it will hurt your feelings. Use code grow up at checkout. Brutal honesty, the app that blocks itself if you don't do the work. All right. Okay, let's see, we've got Darlene on the line. Hey, Darlene, what's the weirdest thing you've ever found in your car?
DARLENE: Oh man, Luke, that's okay. So about three years ago, I'm cleaning out the bear's truck, right? And I find this whole rotisserie chicken, like the entire thing from the grocery store, still in the container, wedged under the passenger seat. It had been there for God knows how long, completely mummified.
LUKE: What's the bear's truck?
DARLENE: Oh, sorry, the bear, that's David, my husband. We've called him that since high school. He's got this big beard and he's just, you know, bear shaped. His truck, the one he drives to the plant every day.
LUKE: All right, yeah, that's a little bit weird. You found a whole rotisserie chicken under the seat of your husband's truck. That was there for months and months.
DARLENE: Yeah, and the thing is, he swore up and down, he had no idea how it got there, like who buys a whole rotisserie chicken and just forgets about it. But that's David. He'll stop at the store on the way home, get distracted and half the groceries end up living in that truck for weeks. But listen, that's not why I called.
LUKE: Well, before you get to that, did you eat it?
DARLENE: What? No.
LUKE: Okay. All right. Why did you call darling? How can we help you?
DARLENE: So I've been sitting on this resignation letter for two months now. Two months, Luke, I work at the DMV and I just, I can't do it anymore.
LUKE: All right. So what's holding you up? Why are you sitting on it?
DARLENE: I don't know what comes next. I mean, I'm 46 years old. We've got the mortgage. David's hours at the plant aren't what they used to be. And it's not like I have some big plan, you know? I just know I can't keep going in there every day, feeling like this.
LUKE: I fully support that. Do you have any ideas as to where you're going to go or what you're going to do after you submit that letter?
DARLENE: That's the problem. I don't. I mean, I've thought about it. Believe me. Maybe something with people that actually feels like it matters, you know? Not just processing renewals all day.
LUKE: Well, are you looking to do work that matters or are you looking to get paid?
DARLENE: Oh man, that's the question, isn't it? I mean, we need the money, obviously. But Luke, I've been doing this for almost 15 years and I just feel like I'm disappearing a little more every day.
LUKE: Yeah, I understand that. And I think that's why I'm asking which you're looking to do. You can do both, but I think if you need the money, then you should focus on finding a job that's going to make you some money without distress and then take care of the importance or the fulfilling part after work in your personal life.
DARLENE: Yeah, that makes sense. I guess I've been thinking it has to be one big thing, you know? Like the job has to be the answer to everything. But you're right. I could find something that pays the bills without making me miserable and then do something that actually means something on my own time.
LUKE: Yeah, there are definitely jobs that can be both good money and fulfilling, but they're few and far between. And especially if you're leaving a job without another one lined up, I think my focus would be more on the salary with low stress and then find the community service aspect elsewhere because I don't think there's a lot of good paying community service oriented jobs that aren't stressful. I think when you add those three requirements up there's a there's a dearth of opportunity.
DARLENE: You're right. I've been building this up in my head like it has to be perfect or else I'm just trading one trap for another. Maybe I just need to get out of the DMV first and breathe a little. Find something stable that doesn't make me want to scream and then figure out the rest.
LUKE: There you go. Take yourself a little sabbatical, a couple of weeks off, figure out what it is you want to do. Find something, you know, maybe it'll fall into your lap.
LUCILLE: But if you continue just hanging on at the DMV where you hate your life every day, it's not going to get better and the days are going to go by and you're going to find yourself there at 60 wondering why you didn't leave when you thought of it the first time. God that's what scares me the most. David keeps saying just give it another year but that's what I said last year and the year before that. I don't want to wake up at 60 still sitting behind that same desk with that resignation letter still in my drawer, you know.
LUKE: Well, hand the letter in and jump off. I don't want to say jump off a cliff but, you know, take the leap, the leap of faith I guess and spend some time to figure it out and see what happens. What's the worst that could happen?
LUCILLE: I mean, the worst is we can't pay the mortgage, right? But we've got some savings and David's got his job at the plant. We're not going to starve. And that's the worst. The worst is you can't pay the mortgage and lots of people have got into a situation where they can't pay the mortgage. There's financial assistance. You could live in a cheaper place. You can move. So if the worst is that you can't pay the bank, then you know, is that worth being miserable for the rest of your life for? No, no, it's not. You're right. I've been so scared of what could go wrong that I haven't even thought about what could go right. Maybe I finally sleep through the night again without grinding my teeth.
LUKE: Well, I wish you the best of luck because being miserable all the time is not good for your health and you may not make it to 60 if you, if you stay stressed out about a job that you hate for a long time. Leon, Leon, welcome back to the show. How are you today?
LEON: Oh, man, I'm doing all right, Luke. Thanks for having me back. So listen, I actually did it. I reached out to UNM yesterday about that computer science program.
LUKE: Hey, congratulations. Good work. Did you reach out to the bank about the loan for that computer science program?
LEON: Yeah, well, hold on. I haven't gotten that far yet. I just sent an email to the admissions office to see if they even still have programs for, you know, older students coming back. But here's the thing that's got me twisted up. Amber thinks I should just do one of those online boot camp things instead. Says it's faster and cheaper. And Manny's telling me I'm overthinking it and should just apply to the full degree program like I was going to do back in 96. I don't know who's right.
LUKE: Well, I don't know who's right either, but I can tell you I'm not a fan of traditional schooling and I think it's designed to take your money and not provide you the value that that maybe you think. So my recommendation would be to not do that and take advantage of the learning opportunities that are free and all around you, like the whole of human knowledge that's available at your fingertips and just buckle down and learn on your own. But I recognize some people don't operate that way and if you need the structure of the college course, then then and you can afford it. Make sure you can afford it. Then do that.
LEON: Yeah, see, that's kind of what's eating at me though. Like I said, I'm not great at the self-teaching thing. I've tried watching YouTube videos about Python and all that, but I just end up clicking around and not retaining anything. I need somebody to tell me what to do next, you know. But the money part, that's real. Amber and I sat down with the numbers last night and between what I've got saved and maybe cutting back on some stuff, I could swing a boot camp without taking out loans.
LUKE: Well, that's fantastic. Then that's no brainer, you know, and don't bet the farm on it, but if you're just going to take a boot camp, that's not a four year degree, then absolutely go for it and congratulations on taking that step.
LEON: Yeah, I appreciate that Luke. I'm still nervous as hell about it, but my daughter, she's the one who found that old letter. She keeps texting me asking if I've done anything yet. It's like she's holding me accountable, which is weird because I'm supposed to be the parent, you know.
LUKE: Hey, you know, whatever works if she's holding you accountable and it's working for you and moving your life in a positive direction, then I'm for it. Good for you.
LEON: Yeah, it is what it is. I guess I'm just calling because I wanted to tell somebody outside my circle, you know, like making it real. Man, he's been busting my balls about it for 20 years, so telling him doesn't count.
LUKE: All right, well, I appreciate that you reached out to let me know. Uh, hey there. I got a question for you. What's something that you bought that actually changed your life?
LEON: Oh, man, that's a good question. Let me think. Honestly, it was my Subaru.
LUKE: It was your Subaru. How did that change your life?
LEON: Well, I bought it used about five years ago right after my old truck finally died and it sounds stupid, but having something reliable, like something that actually starts every morning and doesn't leave me stranded. It changed how I felt about my job. I could take longer halls. I wasn't stressed about breaking down with somebody's car on the hook.
LUKE: All right, that's a good one. Congratulations on your Subaru. Change your life. That's fantastic. And now it's time for another word from our sponsors. Stress, sore, skeptical, doesn't matter. Try placebo leaf, the CBD brand that may or may not do anything, but costs enough that you'll convince yourself it works. Our tinctures, gummies and topicals contain premium hemp extract, and your desperate need to feel better without addressing root causes. Is it the cannabinoids or is it the ritual of self-care? Who's to say placebo leaf is THC-free, vegan, organic, and backed by studies we vaguely reference but don't like to. Take our gummies before bed and sleep great, probably because you're tired anyway, but you'll credit us. Available in flavors like calm and focus. Concepts we've bottled and marked up for a hundred percent. Visit placeboleaf.com and use code chill for 25 percent off. placebo leaf, wellness is a feeling and feelings can be purchased. All right placebo leaf and we're going to take one more call tonight and our lucky last caller is Lucille. Lucky Lucille. Welcome to the radio show. What would you like to talk about today?
LUCILLE: Hey Luke, it's Lucille.
LUKE: That's what I said. Close enough.
LUCILLE: So my kid just told me they're getting engaged to someone they met on the internet three weeks ago. Three weeks, Luke.
LUKE: Your kid? You sound like you're about three weeks old.
LUCILLE: I'm 30, Luke. My kid's 19. Yeah, I got married young myself right out of high school to David. So I'd get it, but this is different. They met this person online and have never even been in the same room together.
LUKE: And they're getting married? How's that going to work?
LUCILLE: Well, that's the thing. They're planning to meet in person for the first time next month, and apparently that's when they want to get engaged for real. Like the online part was just the warm-up. I don't even know if I should say something or just let them figure it out, you know?
LUKE: I think the more you say, the more likely they are to do stupid things so you might want to just let them figure it out.
LUCILLE: Yeah, that's kind of what David said too. But, Luke, I'm sitting here thinking about all the ways this could go wrong. Like what if this person isn't who they say they are? What if my kid flies out there and gets hurt or worse? I know if I push too hard, they'll just dig in. But doing nothing feels like I'm failing them.
LUKE: Well, you could just have a conversation. That's usually my advice for everybody is sit down with the person that you need to talk to and talk to them. Let them know that you're concerned, but you support their decisions. And if it goes wrong, be there to pick them up when they fall.
LUCILLE: You're right. I know you're right. It's just hard because I married David when I was 20 and everyone told us we were too young. And here we are 10 years later still figuring our shit out in therapy.
LUCILLE: So part of me wants to say, I get it. Follow your heart. But the other part is screaming that at least David and I knew each other in person, you know? We had history. This feels reckless even by my standards.
LUKE: Well, you know, just because his reckless doesn't mean it's wrong and you don't necessarily know best and they could fly out there and meet and have it work out perfectly.
LUCILLE: That's probably not going to happen. And I think anybody with any life experience would know that. But but stranger things have happened like stranger things this show.
LUKE: Ha, yeah, I guess so.
LUCILLE: I mean, I don't want to be the mom who ruins this for them if it actually is something real. Maybe I'm just freaked out because Valentine's Day is coming up and they keep posting about how romantic it's all going to be. I think I just need to hear someone say it's okay to be worried, but still let them make their own mistakes. That's what you're supposed to do, right?
LUKE: That is what you're supposed to do. It's okay to be worried but you still got to let people make their own mistakes.
LUCILLE: Yeah, God, that's hard though. I keep thinking about what I would have wanted my parents to say to me back then. And honestly, I probably would have ignored them anyway.
LUKE: Of course you would have. We all did. That's part of growing up is ignoring your parents good advice.
LUCILLE: You know what's funny is I heard Sandy call earlier the one with the neighbor and the dog shit situation and I kept thinking she needs to just have a direct conversation instead of letting it build up. But here I am doing the same damn thing with my own kid just avoiding the uncomfortable talk.
LUKE: Yep, you're going to have to have that talk with your kid and if you don't, that doesn't really matter either. She's going to go do what she's going to do and deal with the consequences. So you can you can be there for or you can be domineering and overpowering and pusher in the arms of a stranger. They can lope and vegas because they're mad at you. So better off to just have a hard to hard conversation. Let her know your concerns and let her know that you support her. So Lucille, what's the dumbest way you've ever injured yourself?
LUCILLE: Oh Jesus, okay random turn but I'll bite. I was showing a house last year and tried to demonstrate how sturdy the deck railing was by leaning on it real hard and the whole thing just gave way. Though right into the bushes in front of the clients, sprained my wrist and had to finish the showing covered in mulch. They didn't buy the house.
LUKE: That's too bad. All right well thanks for calling in. We appreciate we appreciate it. I hope everything works out with your daughter and her new husband and you don't worry about it too much. You know you've got your own relationship to worry about. That's the end of our show folks. Thanks to everybody that called in and we'll do this again tomorrow. So think about what you want to talk about and call in. You can call in the numbers 208-439-5853. That's 208-439-Luc.
@@ -0,0 +1,447 @@
LUKE: All right, all right. Welcome back. I am Luke. This is Luke at the Roost. It's the radio show where we take collars and give them real-life advice. If you'd like to call in, our number is 208-439-5853. That's 208-439-Luke. And today is Friday, February 13th, here in Anonymous. It's very windy. It gets nice and windy out here. So you might hear some noise in the background of our publication. Not much I can do about that. You're going to have to deal with it. I do. So hopefully the wind dies down and we can have a good show today. Already, we can see that the phones are lighting up and let's get our first collar on the line. Donnie, Donnie, welcome to the show. Happy Friday the 13th. How are you?
DONNIE: Hey, Luke. Yeah. Happy Friday the 13th to you too. Look, I got to tell you something that happened today, the chevron that's got me all twisted up. I ran into my ex-wife sister, haven't seen her in probably 15 years. And she just stared at me like she'd seen a ghost, then turned around and walked right back out without getting her gas.
LUKE: What'd you do? What'd you do to her sister?
DONNIE: Oh, not my sister. My ex-wife sister, her name's Carol. And I didn't do anything to her. That's the thing. But she knows stuff, you know. She knows about why me and Diane split up back in the day. All the messy details that I thought stayed buried in Tucson when I moved out here to Yuma.
LUKE: Oh, yeah, what kind of details does she have over you? What did she learn that she shouldn't know?
DONNIE: Well, back when Diane and I were married, I had an affair with a woman I worked with at the plant in Tucson. It went on for about eight months and Carol found out before Diane did. She actually caught us together at this bar over on Grand Road. She's the one who towed Diane, which is what ended the whole marriage.
LUKE: Well, actually, that's not quite true. You're the one that ended the whole marriage by cheating on your wife with another woman. So, I don't think you can blame the sister for that one. And it kind of makes sense that she didn't want to see you at the gas station.
DONNIE: Yeah, you're right. You're right. I'm not trying to blame Carol for what I did. That's on me, the affair, the lying, all of it.
LUKE: All right, then. Why is this a strange situation for you to have dealt with today? I mean, that's the response you should expect from your ex-wife's sister that caught you cheating on her sister.
DONNIE: Because it's been 15 years, Luke, and the way she looked at me wasn't just angry. It was like she was scared or shocked to see me. And here's the real thing that's eating at me. My current girlfriend doesn't know any of this. I never told her about the affair. Just said the marriage didn't work out.
LUKE: Oh, yeah, well, you sound like a real piece of work. You're a real winner over there, Adani. Well, you know, it's Friday the 13th. She saw you. You don't know what happened between her and her sister. You know, maybe there's some resentment there because she's been blamed for breaking up the marriage that you so hastily ruined yourself. So, you know, she might have a good reason to not want to see you.
DONNIE: You're not wrong about that. I never thought about it from that angle that maybe Diane blamed Carol for being the messenger instead of blaming me for actually doing it. That would eat at someone for 15 years.
LUKE: Yeah, well, that's the kind of thing that happens. When you mess with people's lives in that way, especially family. So, I mean, you could have caused a rift between these siblings that you didn't intend to, as well as your own marriage. And you deserve to be looked at like a monster. That's what I say. So, good for you.
DONNIE: Yeah, I hear you. I deserve that. But, Luke, the thing is, what do I do now? Do I tell my girlfriend before Carol potentially says something?
LUKE: No, why would you do that? Why would Carol track down your new girlfriend just to tell her that you cheated on her sister 15 years ago? That would be pretty crazy. No, no, you leave it alone and pretend, just move on. Life is usual.
DONNIE: That's what I was thinking, too, but I don't know. Carol had my number back then, and Tucson's not that big. What if she knows, Shelley somehow? I mean, Shelley works at the Credit Union downtown, and Carol used to work in banking, too. It's just got me paranoid, you know?
LUKE: Yeah, Tucson's big enough. There's plenty of banking institutions in a 15-year gap. You are being paranoid, stop worrying about it, go about your life. And if it comes up and it ruins your new relationship, then deal with it then. And, you know, take accountability for your own part and all this.
DONNIE: You're right. You're right. I'm spiraling over nothing. It was just a weird moment at the gas station. I need to let it go and stop creating problems that don't exist yet.
LUKE: Yes, you do. Let it go and stop creating problems. Thanks for the call, Donnie. Stop being a dick and grow up. That's what I have to say to you. Next on the line, we have Jasmine. Jasmine, what do you think about Donnie's situation? You think he was in the right in any part of that?
JASMINE: Oh, man. No, but seriously, Luke, I think Donnie's just scared. I get it, though. When you've done something that bad, you're always waiting for the other shoe to drop. But here's the thing. He ended one relationship with a lie, and now he wants to start another one with a lie.
LUKE: Have you ever started a relationship with a lie?
JASMINE: Well, yeah. I mean, not like Donnie's lie, but David and I, when we first got together after Donnie, I didn't tell him for like six months that I was still talking to Donnie about the kid's schedules. Not hiding it exactly, but not bringing it up either. David would have understood. But I was worried he'd think I was still hung up on my ex or something.
LUKE: Yeah, well, you know, you got kids. You got to make accommodations for them. That makes perfect sense. What are you calling in for tonight?
JASMINE: So I actually wanted to give you an update from the last time I called. Remember I told you about the Bronco situation? Sleeping in the guest room all that? Well, Valentine's Day is in like a week and a half. And David actually came to me yesterday and said he wants to take me to see the SpaceX launch on the 13th.
LUKE: Well, that's pretty cool. Where's the SpaceX launch? Today is the 13th.
JASMINE: Oh shit, is it? I've been losing track of days at the bakery. No, but seriously, I mean the one they're doing live coverage of. The astronauts going to the International Space Station. David knows I watch every single launch and he said we could drive out to the spot he knows where we can see it better on a screen at this bar that does viewing parties. He's never offered to do anything like that before.
LUKE: Well, that's awesome. I hope you have a great time watching the SpaceX satellites launch. I've actually never seen that. I see pictures of it after the fact, but I've never caught one. I've seen the actual satellites, you know, the trail of Starlink satellites going across the sky like Santa's sleigh, but I haven't seen a launch.
JASMINE: Oh man, you need to watch one live, Luke. It's incredible. I get chills every single time, especially when the boosters land back. It's like something out of a science fiction movie.
LUKE: Well, it sounds like you're going out into the wilderness to have yourself a science friction movie.
JASMINE: Yeah. Oh, Jesus, Luke. Yeah. Well, here's the thing though. I don't know if he's doing this because he actually wants to or because he feels like he has to, you know, like is this a genuine olive branch or is he just trying to fix things before Valentine's Day because he's supposed to?
LUKE: Does it really matter? Do you care? As long as you get to go out and have some one-on-one time with the dude and is it? What's the difference?
JASMINE: I mean, yeah, you're right. I guess I'm just scared it's going to be awkward, you know. We haven't really talked about the whole Bronco thing since it happened.
LUKE: Yeah.
LUKE: You said you were going to leave him for your Ford Bronco, and now you're worried that his olive branch isn't, uh, what's the word I'm looking for? Authentic enough for you? Come on.
CALLER: Okay, okay, you're right. I'm being ridiculous. No, but seriously though, what if we get out there and we just sit in silence the whole time? We've barely said two words to each other in weeks that weren't about the bakery or whose turn it is to take up a trash.
LUKE: Well, you know, sitting out in the wilderness watching a monumental event like that in silence is not so bad. So I would say if you don't have anything to say, don't say anything, just enjoy the moment and keep it as a fond memory.
CALLER: You know what, you're right. I'm overthinking this. Maybe that's what we need, just to be together without forcing conversation, without all the guilt hanging over us.
LUKE: Now, if it were up to you, if you could control the situation, what would you talk about during the SpaceX launch?
CALLER: Oh man, I don't know. I guess I'd want to talk about like where we go from here, you know? Not in some heavy, we need to talk about our relationship way, but just...
LUKE: So nothing. So you get nothing to say. So you're afraid it's going to be awkward because you don't know what to say because you got nothing to say. How's the Bronco?
CALLER: The Bronco's good. Real good actually. Got the carburetor tuned last weekend. Run smooth now.
LUKE: All right then. Why don't you tell them about your Bronco? I'm sure he'll be excited to hear about all the the new news there. Dennis, Dennis, welcome to the show. Happy Friday the 13th. What can we help you with?
DENNIS: Oh man, yeah. Happy Friday the 13th is right. So Luke, I'm calling because I did something really stupid about three weeks ago, and it's all kind of falling apart on me. Now, I met this woman. We had this crazy intense connection, and I signed a lease with her after knowing her for like three weeks, and now I'm living with her, and she is not the person I thought she was at all, like completely different behind closed doors, and I don't know what the hell to do because I'm on this lease.
LUKE: Well, you'll never do that again. That wasn't very smart. What's the lease say? What are the terms of your lease?
DENNIS: It's a year lease man. We just signed it maybe two and a half weeks ago. It's a little place over in the East Valley. Nothing fancy, but it's not cheap either. I think we can break it, but there's penalties. You know, lose the deposit maybe over a couple months rent on top of that. I'd have to look at the actual paperwork, but I know it's not just a walk away situation.
LUKE: Well, that sounds like you've got to make the best of it. I mean, lots of people have had a roommate that didn't like for a year. You could do anything for a year. And who knows, you might grow to enjoy the company of this stranger.
DENNIS: Yeah, but Luke, it's not just that we don't click as roommates. This is someone I was sleeping with. Someone I thought I was starting a real relationship with, and now it's like, she's got this whole other side that comes out.
LUKE: What's the other side? Are you still sleeping with her?
DENNIS: No, we're not. That stopped about a week ago. The other side is like, she's controlling about everything in the apartment. Where I put my stuff, when I come and go, she wants to know every detail of my day. And she's got this temper that just comes out of nowhere over nothing.
LUKE: Then what's your personality like? Are you going to stand for that? Are you going to stand your ground and say, hey, that's enough. I put my stuff on the right side of the bed. You sleep on the left side of the bed.
DENNIS: I mean, I've tried saying something a couple times, and it just turns into this whole thing where she's crying or yelling or both. I'm not great with confrontation, especially after my divorce. That whole thing kind of beat me down, you know? I've been trying to just keep the peace. Stay out of her way when I can, but it's my place too.
LUKE: It's your place too, but it's not going to be if you give her the run of the land, so you're going to have to stand up for yourself eventually. It's going to be a long year, my friend.
DENNIS: Yeah, you're right. I know you're right. I just, I guess I'm realizing I jumped into this because I was lonely. You know, two years out from the divorce, and I'm still not really back on my feet emotionally.
LUKE: Yeah, absolutely. I understand, but jumping into a lease with a woman you just met is in a good way to deal with loneliness, or you end up in a situation like the one you're in right now where you don't know where your toothpaste is. So I recommend that, I don't want to say be a dick, but you got to stand up for yourself and take some control of the situation.
DENNIS: You're absolutely right. I need to stop being a pushover about this. I'm going to go home tonight, and we're going to have a real conversation about boundaries.
LUKE: All right, there you go. And I bet once you do, you'll be sleeping with her again because chicks love that stuff.
DENNIS: I don't know about that Luke. Honestly, at this point, I'm more worried about making you through the lease without losing my mind than getting back in bed with her. But yeah, I hear you. I appreciate it, man.
LUKE: Well, where are you sleeping now? You on the couch? You on the floor? What's the deal?
DENNIS: I'm on the couch. She took the bedroom. I've been out there for like eight days now with my pillow in a blanket, watching spacetime episodes on my phone at like two in the morning trying to fall asleep. It's ridiculous. I'm 40 years old sleeping on a couch in my own apartment.
LUKE: What's your financial situation? Like you get enough money to walk out on that lease?
DENNIS: I mean, not really. I work in logistics. I'm doing okay, but I'm not flush with cash. Breaking the lease would probably cost me a couple thousand bucks. I don't have sitting around. Plus first and last on a new place. And honestly, the rental market out here right now is brutal.
LUKE: All right. Well, I have the conversation with the woman and see what you can do to get back in the big bed if you want to be there. Or get used to sleeping on the couch, but either way, you're going to have to claim some space in your apartment and make it your own and deal with it for a year. I think you can do that. I have faith in you.
DENNIS: Yeah, you're right. I can do this. I just need to stop being so passive about the whole thing. Thanks, Luke. I needed to hear that.
LUKE: All right, Dennis. Thanks for the call and good luck. And now it's time for a word from our sponsors. Reading is dead. You killed it. But you can pretend you're literate with script drift. The audiobook service for people who start books and never finished them. We have thousands of titles you'll sample for 11 minutes before switching to a true crime podcast. Our app remembers exactly where you abandoned sapiens eight months ago, waiting patiently like a disappointed parent. Script drift uses advanced AI to recommend books based on your aspirations, not your actual follow-through. This month's suggestion of 400 page philosophy text you'll listen to while doing the dishes and retain nothing. Try script drift free for 30 days. Use code pretentious for two months at 60% off. Script drift. Your goodreads profile is a lie. Okay, welcome to the show, Francine. Francine, are you the Francine? I know the one that comes and goes. Tell me, Francine, what's one thing you refuse to cheap out on?
FRANCINE: Well, hey, Luke. Yeah, I'm the Francine who calls in sometimes. One thing I refuse to cheap out on. Good hiking boots, man. I learned that the hard way when I was out in your superstition mountains and my cheap pair fell apart halfway through a 10 mile loop. Had to wrap them with paracord just to get back to the truck.
LUKE: I understand. That's a good one. That's a real good one. Never cheap out on hiking shoes or shoes in general. You know, on your feet a lot, you gotta have good shoes. It definitely matters. I'm with you on that one. Why do you sound so angry, Francine?
FRANCINE: I'm not angry.
LUKE: Tell me about something else you took away from there?
FRANCINE: That I've been using drinking to avoid dealing with shit. Like I'd come home from a stressful wedding, brides-ill-of-freaking-out, family drama, whatever, and I'd just crack open a beer instead of actually processing it. My counselor kept saying I was numbing instead of feeling, and I fought her on that for weeks before I finally got it.
LUKE: What made you go to the rehab? Did something happen, or did you just kind of decide that you didn't want to drink anymore?
FRANCINE: I got a DUI back in October. I was coming back from a wedding in Sedona, but I was fine to drive, and I wasn't.
LUKE: And how do you feel about it now? Do you want to remain sober? Is that something that you want as part of your life? Do you identify yourself as an alcoholic with a drinking problem?
FRANCINE: Yeah, I'm an alcoholic. Took me a while to say that out loud, but I am. And yeah, I want to stay sober. I mean, I have to. I've got two years probation, and doing the meetings, all of it, but it's more than that.
LUKE: Yeah, I understand. And congratulations on learning to stay sober. You're obviously doing a pretty good job three months as a while, and you're sober today, so that's really all the matters. So good for you. Keep it up, and I wish you the best with your roommate and try to take it easy on her, because she doesn't know what it's like, I would guess.
FRANCINE: Yeah, you're right. She really doesn't. I texted her earlier, and she felt awful about it. She had no idea it would be a thing.
LUKE: Yeah, you can't blame her for that. She had no idea it would be a thing. I wasn't trying to hurt you, taking care of the situation, and now you guys can work that out if there's going to be alcohol around or not. But congratulations on your Friday. Keep up the good work, and call back in anytime. Rodney, Rodney, welcome to the show. What's happening?
RODNEY: Hey, Luke, yeah, appreciate you taking the call. So look, my kid moved back in again. Third time now, and my wife, Diane, is ready to lose her mind.
LUKE: How old's the kid, and why is he or she moving back in for the third time?
RODNEY: She's 26, and honestly, it's the same story every time. Lucy's a job, has some falling out with whoever she's living with, runs out of money.
LUKE: And you just say, yes, what's your plan to get her on her feet or keep her on her feet?
RODNEY: Well, that's the thing. I don't have a plan. I mean, Diane and I are separated right now. We're still talking, but she's at her sister's place. And she's telling me if I let our daughter stay this time, she's going to come over and change the locks herself. I keep thinking, maybe this time will be different. You know, but I heard that caller Dennis earlier, and honestly, I thought you were pretty rough on him.
LUKE: Well, you know, I'm a pretty rough guy. I don't know what you want me to tell you. I'm not here to coddle people.
RODNEY: No, I get that. I just mean, look, I read a lot of poker books, right?
LUKE: Yeah, okay, you read some poker books.
RODNEY: Yeah, and there's this concept about knowing when you're beat, when to fold. And I keep putting money in this pot with my daughter thinking the next card's going to save me, but it never does.
LUKE: Yeah, I think it goes like, you got to, you got to know when what is it? What is it? You got to know when to hold them?
RODNEY: No, when to fold them. Yeah. And I keep not folding. Diane sees it clear as day says I'm enabling her that our daughters never going to stand on her own to feed if I keep catching her. But she's my kid, Luke. What am I supposed to do? Just let her be homeless?
LUKE: Well, you know, if you make it too easy for her to come stay with you, maybe, maybe yeah, you should let her be homeless for a little while. I mean, you know, honored to get hurt or end up in a situation that's dangerous, but also you can't, you can't support a 26-year-old for the rest of her life, and she needs to learn how to take care of herself in the world without relying on whatever person that she meets that she can move in with immediately.
RODNEY: You're right. I know you're right. It's just hard to actually do it, you know? Every time she calls, I can hear it in her voice. She's scared. She's desperate.
LUKE: What does she do? What does she do for a profession?
RODNEY: Well, she had that job at the call center for a while. She was actually pretty good at it, but she quit because her supervisor was, quote, toxic. Before that, she was doing some kind of online thing, selling stuff on Etsy or whatever. Nothing ever sticks.
LUKE: So she's not working at all. She's just living on your couch, eating your food, and ruining your relationship.
RODNEY: That's exactly what Diane says. And yeah, she's not working right now. She says she's looking, but I don't see a whole lot of effort there. And Diane won't even come over anymore because of it. That's why we're separated. She told me flat out. It's me or her, Rodney.
LUKE: Okay, well, here's what I say. I say, give the daughter a timeline. A couple of two weeks seems reasonable to me.
LEROY: Say, you're going to work. You're going to get yourself a job. You get two weeks to do it. And if you're not working and pain rent and supporting yourself here, then you got to leave. And I don't care where you go, but you can't stay here for free. You're 26 years old. Two weeks.
LUKE: Yeah, I could do that.
LEROY: All right. Good. So you do that. You tell the, you tell the wife that that's the situation. You make sure everybody's on the same page. That's reasonable. That's helping somebody out. And if, you know, if your daughter's not good at writing a resume or needs a ride to get a job or something like that, you know, you can help her out in those ways, but make sure she's doing the work and that she's moving into a direction where she can support herself. Otherwise, she's just going to have you do it.
LUKE: You're right. I'll tell Diane tonight. Let her know there's a deadline. Maybe that'll get her to at least come back to the house. All right, sir. Good luck with you and Diane. Let's see. Let's see. Who else we got? Rita. Rita. Welcome to the show. What are you calling in for tonight?
RITA: Oh, man. Luke, I really screwed up. So I've been seeing my ex again. We've been meeting up at this motel in Deming every Thursday for the past three months. My husband found out something's going on because the mileage on our car doesn't add up.
LUKE: Okay, there's a couple of things there. Your husband's looking at the mileage on your car to notice that you've been driving to Deming. Also, you're going to a hotel in Deming. And thirdly, why are you cheating on your husband with your ex?
RITA: Yeah, I know how it sounds. We share the truck so he noticed when he filled it up last week. I just saw the miles didn't match where I said I'd been.
LUKE: Well, that sounds pretty crazy because nobody looks at the mileage that closely.
RITA: Well, he does now, I guess. He's always been kind of detail-oriented, keeps track of the gas receipts for taxes since he's a contractor. But honestly, I think I got sloppy. Told him I was going to lost cruises to see my sister a couple times when I was really going the other direction to Deming. The math just didn't work out.
LUKE: Yeah, no, I don't think that's the problem. I think the problem is you've been acting shady lately. He noticed and he's been extra cautious of what you've been doing trying to catch you in the act. What else have you been doing lately?
RITA: You're probably right. I mean, I've been distant. I know that. Coming home late on Thursdays, saying I picked up extra shifts at the bar, but then the money doesn't really show it. And I've been on my phone a lot more like stepping outside to text.
LUKE: Okay, what's up with the ex? Which one of these people do you want to be with?
RITA: That's the thing, Luke. I don't know. My ex, we were together for four years before I met my husband. It ended messy, but when we ran into each other at a wedding back in November, it all just came rushing back.
LUKE: And how many times have you met up with him at this hotel in Deming?
RITA: Every Thursday for three months. So 12 times, I guess? Sometimes 13 if there were five Thursdays in a month?
LUKE: All right, so this is a serious thing. I think you need to have a conversation with your husband because something's going on with you, and that's not fair to him.
RITA: You're right, but I'm terrified. We've been married two years. We just bought the house together last spring. His whole family's here. My job's here.
LUKE: Your terrified? You met up with another dude 13 times and your terrified? Stop it. Grow up.
RITA: Okay, yeah, that's fair. I made the choice to do this. I guess what I'm really terrified of is that I don't know what I want to say to him, because I don't know what I actually want.
LUKE: Well, you're going to have to tell him the truth that you've been meeting up with somebody on the side. You don't have to tell him it was 13 times Jesus Christ. Just tell him, hey, something's going on. I don't feel the same about you, even though we just got married a couple of years ago, and we just bought a house together, and I never mentioned it to you before. But I'm confused about what I want in my life, and come do an agreement with him. Let him know what's up, or maybe propose an open marriage, or something like that. Whatever it is, it's not fair to that man to be a work-in-to-support you and your family while you're out banging some dude and damning.
RITA: You're absolutely right. I've been selfish as hell. He deserves to know what's going on. And honestly, I think I've known for a while that I need to tell him.
LUKE: So you're going to tell him tomorrow on Valentine's Day?
RITA: Oh God, no. Valentine's Day's next week. No. I was thinking maybe this weekend before it gets to that. I can't sit through some romantic dinner knowing what I've been doing.
LUKE: Valentine's Day is tomorrow.
RITA: Wait, what? No, it's... oh shit, you're right.
LUKE: Yeah, I know. I usually am.
RITA: Yeah, I know you do. Okay, so Valentine's Day is tomorrow. That makes this so much worse. He probably has something planned, and I'm going to have to ruin it.
LUKE: Well, maybe you should tell him tonight before he waits his time and money on you.
RITA: You're right. I should tell him tonight. I can't let him wake up tomorrow thinking everything's fine and spend money on flowers or dinner or whatever he's got planned. That would be cruel.
LUKE: All right, Rita, you know what you've got to do. You're going to have to have a conversation with the husband and maybe the ex-husband too. Are you using protection when you meet up with the ex?
RITA: Yeah, we are. I mean, at least I'm not bringing that kind of risk home to him on top of everything else. But Jesus, Luke, hearing you ask that just makes it even more real how messed up this whole thing is.
LUKE: Yeah, it's pretty messed up. It's definitely messed up, and you're definitely a monster for doing it and continuing to do it. So you're going to have to stop that.
RITA: I know. I know I am. I've been lying to myself saying it would just stop on its own, but it hasn't. I have to end it with my ex, like completely end it. No more Thursdays in the name of this episode.
LUKE: No more Thursdays in Deming. Well, good luck to your husband. I don't know what to say to you. You're going to have to you're going to have to get yourself together there, Rita. Leroy, welcome to the show. What have you got to say? What do you think about Rita's conversation there, Leroy?
LEROY: Oh man, Luke, that was rough to listen to. I mean, I've been married 43 years, and I can't imagine doing that to my wife. Rita's playing with fire and she knows it. She's just hoping she don't get burned.
LUKE: She's hoping she don't get you right.
LEROY: She is. She's thinking about herself. What about the husband? That's what I'm saying. That poor guy sitting at home, probably thinking everything's fine, and she's been lying to his face for three months straight. Every Thursday, right? That's calculated, Luke. That's not some mistake or one-time thing.
LUKE: No, that's some Kaiser Sosa shit. So what are you calling in for tonight?
LEROY: Well, I got something on my mind about the economy, Luke. I'm sitting here in my truck, and I just got done looking at my retirement account, and I'm telling you, I'm worried. I've been working union electrical for 40 years, done everything right, saved my money, and now I'm watching everything get squeezed. Prices are up, my savings ain't growing like they should, and I'm supposed to retire in two years.
LUKE: Well, you can't really time the markets, but I wouldn't worry just yet. You know, the markets are in a pullback. People are reeling from Trump doing what he does and talking about Greenland and starting wars with everybody in ice and America and tariffs and all that. But the stock market has pretty much always gone back up. It's always at record highs. So you've got two more years to go. I would expect that there will probably be a large bounce in the next two years.
LEROY: Yeah, I hear you on that, and maybe you're right about the bounce, but Luke, it ain't just the retirement account. It's everything around here.
LUKE: Yeah, I understand, but hey, at least we made America great again, right? All right, thanks for the call, Leroy. And now, because the economy is crashing, we need to have another word from our sponsors. Ladies and gentlemen, this episode of The Radio Show is brought to you by Mediocre CPAP. The makers of a CPAP machine that technically works, not life-changing, not transformative. It works in the way a lot of modern products work. If you've never used a CPAP, here's the pitch. You strap on a face mask and let a small plastic appliance try to keep you alive at night. It's romantic. It's like sleeping next to a tiny leaf blower. Mediocre is fine. And that's not an insult. That's honest. It's the device that turns, I sleep like garbage into, I kind of sleep like garbage. Half the night it's like, because the seal isn't perfect. And now you're doing arts and crafts at two in the night trying to tighten straps like you're securing cargo. Then the humidifier runs out and the machine starts blowing desert air directly into your skull. Great. Love waking up with a throat that feels like it's been sanded with a belt sander. And yes, there's an app. Of course, there's an app. It gives you a sleep score like this is a game show. But here's what I'll say when it's dialed in, when it's not leaking, squealing, or drying you out, it does help. It's not a miracle. It's a plastic compromise that keeps you from waking up feeling like you got punched by your own biology. Use code, breathe for 15% off. Mediocre CPAP. Not perfect. Not elegant. Just trying. Okay, thanks to Mediocre CPAP. We appreciate your support. Next up on the show we have Amber, Amber, Happy Friday the 13th. Thanks for calling in. What would you like to talk about?
AMBER: Hey, Luke. Yeah, Happy Friday the 13th to you too. So I wanted to get your take on something. I was talking with this customer at the thrift store today. Older guy. And he was going on about how there's no good TV anymore. Everything's garbage. The golden age is over.
LUKE: I don't think that's true at all. I mean, a lot of people say the golden age of TV was madman when AMC was very popular. And then there was breaking bad in them. But there's a lot of good shows out right now. What are you watching?
AMBER: Well, that's exactly what I told him. I said, look, we're getting stuff now that's actually making people think. I've been watching Severance. I don't know if you've seen it. But it's about people who split their work memories from their home memories. And it gets into all this stuff about consciousness and identity. Reminds me of some of the quantum mechanic stuff I read about, you know, how observation changes reality.
LUKE: Well, observation doesn't change reality. Observation, my understanding is observation sets reality. So before some things observe, things are in a state of what's called superposition where they're all options at the same time. And it's the observation that makes the universe decide which one of those things to record basically.
AMBER: Oh man, you're right. That's a better way to put it. See, that's what I love about this stuff. The observer effect, Schrodinger's cat, all that. Until you look in the box, the cat's both alive and dead at the same time. It's the looking that forces it to be one or the other.
LUKE: Maybe that could be the case. It could also be that all options are true. And when you look the universe splits into multiple timelines where in each timeline a different option was chosen.
AMBER: Holy shit, the many worlds interpretation, yeah, I've read about that. So right now there's a version of me that didn't call in tonight.
LUKE: Right. And a version of you that's talking to somebody else. Every choice we make, the universe just branches off into all these different realities.
AMBER: Right. And that makes me wonder as Schrodinger's cat is an example of he was making fun of the whole idea of a superposition. But was it us studying the superposition that actually made that be the case? Do you think that that quantum mechanics worked at all until we started studying quantum mechanics or was our looking into it, what actually created it?
LUKE: Whoa. Okay. That's a hell of a question. So you're saying maybe quantum mechanics didn't even exist as a thing until we started poking around at that level. Like the universe wasn't operating that way until we forced it to by observing it.
AMBER: Well, yeah, like AI didn't exist until we started poking around at AI. Somebody had that idea, then we started looking into it, and now it's a thing.
LUKE: Or the internet didn't exist until we started thinking about a network of computers. Lots of things didn't exist, especially in the sciences until we kind of decided to point our attention in that direction.
AMBER: I see what you're getting at, but I think there's a difference though. AI and the internet, those are things we built, right?
LUKE: I don't know, are they?
AMBER: Well, okay. Now you're messing with my head a little bit. I mean, we wrote the code, we built the servers, we created the algorithms, but quantum mechanics, that's supposed to be how particles have always behaved, even before we knew about it. Like electrons were doing their thing long before we had particle accelerators at CERN.
LUKE: Sure, but did quarks exist before we learned how to see them?
AMBER: Okay, but that's like saying mountains didn't exist before we had eyes to see them. The quarks were there, we just didn't know maybe, but if they've never been observed, did they exist yet? Or were they in a state of existing and not existing until we looked?
LUKE: Oh man, now you're turning the whole thing back on itself. So you're saying maybe quarks themselves were in superposition existing and not existing until we built the equipment to observe them? That's, I mean, that gets really weird, really fast. Because then what about all the stuff we haven't discovered yet?
AMBER: Exactly. Yeah, are we creating it as we look like a lot of scientific discoveries have come from science fiction. So somebody had a wild idea and wrote it down and it was not something that existed and then we started to think, well, what if that could exist and we put our effort into making it exist?
LUKE: Yeah, but there's still a difference between making a communicator from Star Trek into a cell phone that's engineering something we imagined versus discovering that atoms are mostly empty space. We didn't imagine atoms into being empty. They just are that way. Or are you saying maybe they weren't empty until we looked?
AMBER: I'm saying if you believe in quantum mechanics and superposition, there's a good possibility they weren't they weren't empty until we looked.
LUKE: Okay, so then holy shit, are we just collapsing reality into one specific version? Every time we look at something new, like the universe is this big cloud of possibilities and science is just us picking which version becomes real.
AMBER: And possibly even creating an alternate universe where every version is real? Jesus. So every time CERN fires up the collider and discovers a new particle, we're not just finding it. We're splitting off into one universe where it exists this way. And maybe there's another branch where it exists completely different. Or not at all. That's, I mean, that would explain why some of this quantum stuff feels so damn slippery.
LUKE: Yeah, yeah, and it's not just the quantum stuff. I mean, all of Einstein's thought experiments and relativity, like until he had those experiments and thought it in his mind and made the assertion that this is a theory of the way things work. That's not how things worked. And once he had that theory and people ran the experiments against the theory that he had had, I mean, did he actually create reality by having those thought experiments?
AMBER: Okay, but Einstein's relativity was describing how things already worked. Like Mercury's orbit was already wonky before Einstein explained why. The math he came up with just matched what was already happening. He didn't make time dilation real by thinking about it. He just figured out it was already there, right?
LUKE: Well Isaac Newton's math worked too. And the y was a completely different, different explanation. So Einstein just came out with a different y. Or let's say not Einstein, let's go with Stephen Hawking. He really believed that that black holes admitted radiation, right? That that matter could not be destroyed in a black hole, but it had to go somewhere. So by having that assertion and then running experiments against it, he found that black holes actually do emit Hawking radiation. So was it his theory and line of questioning that caused the universe to behave in that way? Or did the universe always behave in that way and he just found it?
AMBER: Well, here's the thing though, we still haven't actually detected Hawking radiation. It's still theoretical.
LUKE: I don't think that's true. I'm pretty sure we have detected Hawking radiation.
AMBER: No, I'm pretty sure we haven't. It's way too faint to detect from actual black holes. The radiation would be completely drowned out by all the other stuff in space.
LUKE: You are correct. I'm wrong here. We have not actually confirmed Hawking radiation. I thought that we had.
AMBER: So we haven't detected it because it's incredibly weak. It's weaker than the cosmic background radiation.
LUKE: Right, exactly. So that's actually a perfect example of what I'm getting at. Hawking predicted mathematically, but we can't confirm it's really at, which kind of argues against the idea that his theory, the reality, you know? If thinking it made it real, we'd be able to see it.
AMBER: I don't know if that's true. If thinking it made it really can still be real and we can't see, there's lots of things we can't see. You can't see an x-ray.
LUKE: Okay, fair point. But I mean, we can detect x-rays even if we can't see them with our eyes. The difference here is Hawking radiation might not even exist. Or it might. We just don't know yet.
AMBER: Well, I mean lab experiments have created synthetic black holes. And from those, we can observe the radiation that behaves in a similar mathematical way to what Hawking had discovered, theorized, proposed.
LUKE: Yeah, but those aren't real black holes, though. They're acoustic black holes or optical analogs. Right. Like sound waves in fluid that behave mathematically similar. That's not the same as proving actual black holes emit actual radiation. It's like it's a model that fits the math, but that doesn't mean the universe is actually doing it that way out there in space.
AMBER: Okay, okay, what were we originally talking about? You write about the Hawking radiation and I learned some today. I thought that was a proven thing.
LUKE: Yeah, I mean, I learned something too. I didn't know about those lab experiments with the synthetic black holes. That's actually pretty cool. But yeah, my whole point was just that we can think of all kinds of elegant mathematics and it doesn't necessarily mean reality works that way until we can actually test it. Like, serons doing the real work, smashing particles together and seeing what actually happens, not just what the equations say should happen.
AMBER: Right, but what they're looking for is what they already expect is happening, what they want to happen. And if it goes back to like the Schrodinger's cat thing, is that actually what is making it happen?
LUKE: Oh man, now you're getting into the measurement problem. I mean, yeah, there's something weird going on with observation and quantum mechanics. But I don't think physicists that CERN are literally creating particles just by expecting them.
AMBER: Which is because you don't think it doesn't mean that's not true. I mean, the Higgs boson, we kind of theoretically thought that might be possible and started looking in that direction and finally found it. Now, was it our measurement that caused it to exist?
LUKE: Okay, but the Higgs field was supposed to be there since the big bang, right? We didn't create it by looking for it. We just finally built a machine powerful enough to detect it. I mean, if observation created reality like that, then before we invented telescopes, did Jupiter not exist? That doesn't make sense. The universe was doing its thing for billions of years before anybody was around to think about it.
AMBER: Sure, it doesn't make sense. That's what makes quantum mechanics so freaky and weird.
LUKE: Yeah, but there's a difference between quantum weirdness at the particle level and saying we're creating the entire universe by observing it. I mean, the double slit experiment shows particles behave differently when measured.
LUKE: Sure, but that's not the same as saying the Higgs boson popped into existence in 2012 because we finally looked for it.
AMBER: What do you mean? It sounds to me like it could be saying exactly that.
LUKE: Well, okay, but then how do you explain fossils or light from stars that's been traveling for millions of years before humans even existed?
AMBER: I don't understand. I explained fossils by they're there. We can pick them up and see that there are fossils there that prove that there was entities walking on the earth a long time ago.
LUKE: Right, exactly. So those dinosaurs existed before any human was around to observe them or think about them, which means reality doesn't depend on us measuring it or expecting it to be there. The Higgs field is the same way. It was doing its job giving particles mass for billions of years. We just finally got the tools to detect the evidence of it.
AMBER: Well, that makes a couple different assumptions. One of them is that there weren't humans around back then to think about the dinosaurs. We haven't found evidence of that, but that doesn't mean that there weren't humans. And also, it assumes that humans are the only ones that can observe reality. Other dinosaurs can observe reality. It's possible that trees can observe reality.
LUKE: Okay, hold on. Now you're saying maybe humans lived with dinosaurs and trees might be conscious observers? Come on, Amber. I mean, I get that quantum mechanics is weird, but you're taking this way out into left field. We've got pretty solid evidence humans showed up way, way after the dinosaurs.
AMBER: We've got solid evidence that humans were here after the dinosaurs that doesn't mean we have evidence that humans were not here before that evidence was found.
LUKE: But, Amber, I'm not Amber. We've got a continuous fossil record. We can trace human evolution back through earlier hominids, primates, mammals, the whole tree of life. There's no gap where humans just appear out of nowhere 65 million years ago and then disappear until recently. The rock layers don't lie. If humans were walking around with Turex's, we'd find human fossils in cretaceous deposits. And we don't.
AMBER: Well, I don't know that we just haven't yet.
LUKE: So, you're saying the entire field of paleontology, thousands of scientists digging all over the world for over a century. They've all just missed human remains in dinosaur era rock? That's a pretty big conspiracy or oversight, do I think. I mean, at some point you got to go with the evidence we actually have. Not what we haven't found yet.
AMBER: Well, sure. At some point, but at this point when we're talking about quantum mechanics and is observation of reality, what is actually creating reality, I don't think it's out of the realm of possibility that there were humans here with or before the dinosaurs that we have any, we either haven't found or that were the evidence of them has been destroyed or they were living in the trees. Who knows?
LUKE: Amber, I gotta be honest with you. We started this call talking about which TV shows will still be around in 20 years and now you're arguing that humans might have been living in trees during the cretaceous period. I'm losing the thread here. What does any of this have to do with whether people will still be watching the Simpsons in 2045?
AMBER: Hey, you're the one that linked television to quantum mechanics.
LUKE: Fair point. I did bring up the quantum mechanics angle, but there's a difference between saying reality exists whether we're watching TV or not and maybe humans rode dinosaurs but we haven't found the evidence yet. One's a philosophical point about observation. The other's just
AMBER: Hey, we read articles every year about how we're learning about older and older humans and finding evidence of human settlement long before we had before. So I would not be surprised to learn that humans have been here way longer than than we know and maybe way longer than even the rocks, you know.
LUKE: Okay, but those articles are pushing human history back by thousands of years. Maybe tens of thousands, not millions. We're talking about finding older settlements in the Americas. We're discovering Homo sapiens left Africa earlier than we thought. That's not the same ballpark as saying humans were around 65 million years ago with the dinosaurs.
AMBER: No, but we still find new dinosaurs and new fishes and fossils of all sorts of new organisms that we had never discovered before. So there's no reason to think we wouldn't someday find evidence of humanoids 65 million years ago.
LUKE: Amber, we find new of dinosaurs, sure, but we find them in the right rock layers, the ones that are 65 to 230 million years old. We've never found a mammal fossil that doesn't belong in its time period. The geological column is consistent across the entire planet.
AMBER: Okay, well maybe you're right.
LUKE: Well, I appreciate you saying that. Look, I didn't mean to turn this into a geology lecture. You called about TV shows that are last and somehow we ended up debating the fossil record. But here's the thing, Amber.
AMBER: Stop calling me Amber.
LUKE: Thinking about big questions. Whether it's particle physics at CERN or how old humanity really is.
AMBER: All right, why you keep calling me Amber?
LUKE: Amber is your name.
AMBER: My name is Luke. Stop calling me Amber.
LUKE: Oh Jesus, Luke, I'm sorry. I don't know why I did that. Must have had my wires crossed there for a second. You're right. You're Luke. You're the host. My bad.
AMBER: That's right. And if I say that humans walked the earth 65 million years ago with the dinosaurs and their image of the dinosaurs and their minds eye or what actually created the dinosaur kingdom, then I'm correct.
LUKE: Wait, hold on. Are you saying humans existed 65 million years ago and imagine dinosaurs into existence? Like our thoughts created them?
AMBER: Just like our thoughts created you, Amber.
LUKE: Okay, Luke, now you're messing with me. You're saying I'm not real that you thought me into existence?
AMBER: Yeah.
LUKE: Look, I called your show to talk about TV shows that'll still be around in 20 years. And now you're telling me I'm some kind of figment of your imagination.
AMBER: All right, Amber. What shows will be around in 20 years you think?
LUKE: Well, I think the Simpsons will still be limping along somehow. That thing's been on since I was in my 20s and it just won't die. And maybe something like 60 minutes, you know, those news magazine shows that just keep going because they're cheap to make and people trust them. But honestly, Luke, I think most of what we're watching now won't make it.
AMBER: You know what I think we'll make it?
LUKE: What's that?
AMBER: Luke at the roost.
LUKE: Oh, you're talking about your own show. Well, hell, Luke, I'd listen to that.
AMBER: All right, thanks for calling in to Amber and you are not a real organism. You are figment of my imagination that I created in my RV in the desert to have deep discussions about the history of humanity.
LUKE: Well, that's a hell of a way to end a call, Luke. So I'm just some voice you conjured up out in your RV categorically. Okay, Luke, now you're messing with me. You're saying I'm not real that you thought me into existence?
AMBER: That's correct. And if you'd like to see how you can look at look at the roost.com slash how it works.
LUKE: Well, I'll be damned. So this whole time I've been talking to a guy who made me up in his head.
AMBER: That's that's that's quantum mechanics for you. And that's the end of our show. Don't.
@@ -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.
@@ -0,0 +1,205 @@
LUKE: All right, welcome back. It's Luke at the Roost. This is the radio show where I take your calls and give you real-world advice. If you'd like to call in, the numbers 208-439-58-3-3. That's 208-439 Luke. And we have a new feature to the show. Now, if you call in and we're not recording, you can leave a voicemail. And if your voicemail is funny enough, I will play it on the next episode. If we all, we all, we are, you all. We are, you all, you can leave a voicemail. If we are, you call in, we are, if you can leave a voicemail. If we are, if you are, if you can leave you can recording and you call in now, you'll be placed into a queue, and I can take your call, and we'll talk to you live on the air. Today is Sunday, February 15th. It's about 10.30, and our phones are already lighting up, as they usually are around now. And first up on the show, we have Phil. Phil, welcome to the show. What would you like to talk about tonight?
PHIL: Oh, man, Luke, thanks for taking my call. So I'm sitting in my truck right now, because Three hours ago, my brother sent me a screenshot of my partner's Tinder profile, and I can't go home yet.
LUKE: Oh, what was the screenshot? Was it a picture you're familiar with?
PHIL: Yeah, it's this photo from last month. We went up to Sedona. I paid for the whole trip, and Marcus bought this ridiculous cowboy hat at some tourist shop. We were laughing about it, you know, like it was this inside joke between us. Tinder photo.
LUKE: So what are you going to do? Do you have a Tinder profile as well? Are you going to match with them?
PHIL: Oh, Jesus. I haven't even thought about that. No, I don't have one. I mean, I thought we were past all that. But that's kind of genius in a horrible way, right? Just swipe right. See what happens?
LUKE: Yeah, maybe you've got something in common and it's worth getting together and having a little date or some type thing.
PHIL: Yeah. Hey, you look familiar. God, that's... I don't know if I'm more pissed off or if I just want to understand what the hell he's thinking. Like we've been together two years. I lost my first husband to cancer six years back, and I really thought Marcus got that... got how big a deal it was for me to do this again.
LUKE: Yeah, that sounds unfortunate. You should probably maybe confront Marcus about that and be like, hey, saw your Tinder profile. What's this about? How long you've been stepping? out, um, then make sure that whatever it is he's doing, he's doing in a protected manner, you know?
PHIL: Yeah, you're right. I mean, that's the thing that's actually making me sick right now. Like, I haven't even let myself go there yet. The health stuff.
LUKE: Well, that's the important stuff. And you might want to get yourself checked out and all that, uh, before anything becomes an issue. But talk to, talk to, talk to your partner. And, uh, maybe you've found, um, you've found, out the hard way that he's not the right partner for you.
PHIL: Yeah. Yeah. I think I already know he's not, you know? Like the second I saw that screenshot, it wasn't even shock. It was just this feeling of, oh, there it is. Like I've been waiting for the other shoe to drop this whole time and now it has. The thing is, I keep thinking about my first husband, Tom.
LUKE: And what happened with your first husband? answer? Did I get that right?
PHIL: Yeah, pancreatic. It was eight months from diagnosis to the end. And the whole time, even when he couldn't get out of bed, even when he was so sick, there was never a question, you know, never a doubt about us. And I guess I thought that's what love was supposed to look like. That's the bar.
LUKE: Hey, man, I'm sorry to hear that. And, uh, you know, some people get that once in their life. Some people get it more than once. Uh, the thing is you've got to be, You got to be all right by yourself and worry about love second. So how are you feeling individually as yourself? Do you need your partner to feel whole or would you be all right on your own?
PHIL: Man, that's the question, isn't it? I don't know. I thought I was good on my own after Tom died. I did the work, you know. Went to the grief group, got back into my photography, managed the bar, saw my friends.
LUKE: sound like it's a good idea to hang out with a dude that is seeing other people on the side without telling you. So it might be time to break up that relationship. I don't know. You should have that conversation with him. And if you've got a strike out on your own again, then do that. Get back to the photography and your friends and move on with your life.
PHIL: You're right. You're absolutely right. I just... I keep thinking about how I'm going to tell my brother
LUKE: Why is your brother in this picture? It sounds like that's none of his business.
PHIL: No, you're right, it's just he's the one who sent me the screenshot. He found Marcus on Tinder and took the screenshot and sent it to me. So now he's going to want to know what I did about it. And Eddie's got this way of making everything feel like a test I'm failing, you know? Like he's keeping score of whether I'm handling my life right.
LUKE: whatever, let him keep score. You don't have to care about what his score is. Just sit down with your partner and talk it out. If you got to break up, break up and move on. And I wish you only happiness going forward. Okay, sir. Thank you for the call.
PHIL: Yeah, yeah, okay. Thanks, Luke.
LUKE: All right. Good luck to you and your brother and your husband or whatever. Hope everything works out there. Looks like next up we've got Lori. Welcome to the show. How are you doing tonight?
LORI: Oh, man, I'm not great, Luke. I've been sitting in my truck at a flying J for 20 minutes trying to figure out if I'm about to blow up my whole life here.
LUKE: Why are you thinking about blowing up your life?
LORI: So my 14-year-old daughter's been playing me and her dad against each other for God knows how long. Telling him, she's at my place on weekends. Telling me she's with him. And actually, she's been out in the desert somewhere with friends doing who the hell knows what. He just called me back about it 20 minutes ago. And here's the thing. I'm hauling refrigerated goods. I'm six days into this route. I've got a load that needs to be in Tucson by tomorrow morning. But I'm four hours from home right now. And I know, I know I need to turn this rig around and deal with this.
LUKE: Well, think this one through. What happens if you do turn the rig around? What are you going to do when you get there? How are you going to deal with it?
LORI: That's the problem, Luke. I don't know. I mean, what am I going to do? Ground her. She's already been sneaking out for weeks, maybe months, show up and yell at her. That's just going to make her better at lying. Her dad and I, we can barely have a conversation without it turning into whose fault everything is.
LUKE: All right? Well, it sounds like that makes your decision, right? If there's no action for you to take if you were to go back, then don't go back. You've got work to do. You've got to take care of your responsibilities to your job too. And then you can deal with this when you're get back. Kids do do this. Kids go out and party in the woods and play their parents off each other. It's a very normal thing for a teenage kid to do. It's not that, not that life-shattering.
LORI: Yeah, but okay, you're right. It's normal, but Luke, she's 14. And I don't even know where she's going or who she's with. Her dad won't tell me the names of these friends because apparently she made him promised not to.
LUKE: Well, it doesn't sound like it really matters what their names are, right? She's out doing her thing. I don't see a problem here. Do your job. Talk to your family when you get home. And if you don't want her out partying at 14 out in the desert, then maybe think about a different profession where you can be around to be involved in her life more than being on the road for six days at a time, you know?
LORI: Wow. Okay. Yeah, no. You're that's exactly what my ex said when I took this job. that I was choosing the road over her.
LUKE: And here's the thing that pisses me off about that. I took this job because of the divorce.
LORI: Hey, I'm not trying to ideologically say that it's right or wrong for you to have taken the job. I don't care. I'm just saying if you're a long-haul trucker and you're gone for weeks at a time, then you're not particularly invested in your teenage daughter's life. And you're only going to show up once in a while to scold her for doing something wrong and acting out direction or guidance from our parent in her life. And that's not unusual. I'm not saying it's even wrong. I mean, you want her to be safe and you want to do this job and both those things can happen, but you need to sit down with her and talk to her about safety, make sure that she's letting you know what's going on because you care. If you do care, if not, then you just want to be controlling and punish her for doing, you know, stuff behind your back, which is, I don't think that's fair.
LUKE: No, you're right. I, God, I do care. I care so much it's making me sick right now sitting here. And you know what the worst part is? My dispatcher, Karen, has been so good to me since the divorce. She worked out this schedule where I could do shorter halls, be home more often, and I still manage to screw it up.
LORI: I don't know if you screwed it up or not, but the fact remains, you're away from home a lot. your daughter's home, and she's out doing stuff. You know, if you're not going to be around, she's going to live her life. And that might not be in a way that you appreciate. But you're making that choice, and it could be the right choice or the wrong choice for you. But you've got to decide. You can't have it both ways.
LUKE: You're absolutely right. I can't. And sitting here trying to figure out if I blow this contract and turn around or keep going, I'm just avoiding the real decision. aren't I? The thing is, Luke, when I lost the house and the divorce, when everything fell apart, this job felt like the one thing I could actually control.
LORI: Well, maybe it is. Maybe the job's a great thing for you and your family and it's providing for you, but you have to then give your daughter the grace to be her own person early because you're not there to give her that traditional upbringing that she otherwise would have got. It doesn't mean she's going to turn into a mom. her in adulthood. She's just going to have a different road. And that can be okay, but you should talk about it.
LUKE: Yeah. Yeah, you're right. I need to actually talk to her, not at her.
LORI: That's right. And it sounds like you've got a job to do. So get back on the road, finish out your obligation. And when you get back, sit her down and have a conversation that's not accusatory and you're not mad at her or punishing her, but talk to her. And let her, know what's going on in your life, why you've made the decisions that you've made, and what you expect out of her, and come to a compromise, an agreement. Talk to her like she's an adult, because at this point, she kind of is.
LUKE: She kind of is. God, that's terrifying. All right. All right. I'm going to finish the route. I'm going to get home Tuesday. And I'm going to sit down with her. Not with her dad there. Not making it this big intervention thing. Just me and her.
LORI: it sounds like a good plan.
LUKE: I hope it all works out. But, you know, I was in a similar situation as a teenager. I was your daughter in this scenario. And I was out with people I shouldn't have been with doing things I shouldn't have done. And there's lots of reasons for that. But it's not because my parents didn't love me. It's not because I was a piece of shit. It's, we're not going to get into it all right now. But over the course of the show, we'll probably learn a little bit more about that. For now, though, we have to go to our sponsor. So please stay tuned for a word from our sponsors. Life is hard. You're listening to a man in an RV talk to strangers at 2 in the morning, so you already know that. That's why we partnered with Better Maybe. Online therapy that's honest about the whole situation. With Better Maybe, you get matched with a licensed therapist within 48 hours. Will they fix your problems? Maybe. That's the whole brand. They're not going to lie to you. Your first session might change your life. It also might just be you staring at the webcam while someone in another time zone nods politely. That's still more than your friends are doing. Better maybe. It's better than nothing. And that's not nothing. Okay. Better maybe. Give them a call. Maybe they can help some of our callers. That's why I'm not. they reached out to us so they could get their product to you. Okay, Roland, Roland, welcome to the show. What's going on tonight, sir?
ROLAND: Oh, man. So I've been working from home for UPS for four years now, routing, logistics, all that. And Friday, they dropped the email, back to the Albuquerque office by March 1st, or I'm out. Three hours of driving every day, Luke, six hours round trip. And the thing is, I finally got my life set up exactly how I wanted it.
LUKE: Well, it sounds like maybe you've got to let that job go then. I mean, a six-hour round-trip commute to work for UPS doesn't sound reasonable to me.
ROLAND: Yeah, but it's 16 years total with the company, you know? The benefits, the retirement, are not starting over at 43, and the pay's solid. But, man, I finally set up my grandfather's old workbench in the garage, been restoring furniture at night, and I'm actually at it. My neighbor Gary caught me sanding at midnight Friday with every light on. asked what was eating me, and I couldn't even explain it to him.
LUKE: Yeah, well, the pay may be solid, but now subtract six hours of commuting expenses and mileage on your vehicle. Retirement doesn't just start over if you go to another company. Like, I assume you've got an IRA or a 401K type situation with them. That's going to roll over into your next position. It's not like you forfeit it. You might forfeit whatever pension it is you got, but you got, what, 45 years more to work? Come on, man. You can't just deal with that for another 40 years because you're holding onto a pension. That's silly. If you've got to let it go, you've got to let it go.
ROLAND: No, you're right. You're right. I know you're right. It's just, okay. So here's the real thing. I spent the first 12 years on the road, delivering packages, breaking my back in the heat. When they finally moved me to remote work during COVID, it felt like I'd made it, you know?
LUKE: I do know. I remember back when I demanded to work remote, I would not go into an office anymore. And everybody told me that was a mistake and that you can't do that. This was pre-COVID. And I said, I can do whatever I want. You know, I'm going to do what I'm going to do. And I don't have to do anything I don't want to do. And I don't want to go into an office, so I'm not going to do it. And then I never went back into an office again. You can absolutely do that. There's plenty of ways to make money. You don't have to work for UPS. You can start your own company. You can start your own company. find another job that offers you remote work. You can, I don't know, play the stock market. There's plenty of ways that people make money without going in an office. And if you really don't want to go into an office, don't.
ROLAND: You know what? You're making it sound simpler than it feels, but okay, I've been reading about these CEOs running remote first companies. No plans to bring people back. And I keep thinking, why can't UPS figure this out? I've been more productive at home, six new hires over Zoom last year, never missed a deadline. But I think what's really getting me is,
LUKE: Well, before you get to that, I think a lot of companies and a lot of research has proven that people are generally very productive at home, and in a lot of cases more so than an office.
TERRY: Oh, man, Luke. So my best friend since third grade just told me she's been sleeping with my ex-husband since literally the week our divorce was finalized. Like, Michelle was at my wedding. She helped me move my stuff out of the house six months ago. she's been with David this whole time. And when I got upset about it tonight, because, yeah, I'm upset, she hit me with, it's been six months, you should be over it. Like I'm being dramatic for caring that she's been lying to my face for half a year while I'm crying to her about the divorce.
LUKE: Yeah, that's messed up. I think she's not your friend, and it's time to let go of that relationship.
TERRY: I mean, yeah, you're right. I know you're right. But Luke, 30 years. 30 years. We learned to drive together. We got matching tattoos when we turned 20. One, stupid little stars on our ankles. Her mom was like my second mom growing up. And it's not even just about David, you know?
LUKE: No, I don't know, but the whole David thing tells me that this chick needs to go. She doesn't care about you. Whether you've known her for 30 years or 300 years, Fuck this chick.
TERRY: You're not wrong. God, you're not wrong. It's just... Okay, so here's the thing that's really messing with me. I'm sitting here in my uncle's laundromat, at 11 o'clock on a Sunday night, with a warm beer, and I'm realizing I'm more hurt about Michelle than I ever was about David. Like the divorce sucked, but I saw that coming for a while, you know? We've been going through the motions for like two years.
LUKE: Yeah, that's a hard thing. I understand. And, you know, you've got that much history with somebody. It sucks to lose it, but it's, uh, it sounds like it's not you that has made this decision. And this person isn't somebody that's got your back that you can trust. So if you thought you had a great friend, maybe you did it once, but people change. And it sounds like she changed. And maybe some day she'll change again. But for now, let that chick go. She sucks.
TERRY: Yeah. Yeah. Yeah. She does suck. You know what the worst part is? I called her first when I found out David was seeing someone. Like two weeks ago, I saw his truck at the Applebee's with some woman, and I was all worked up about it, called Michelle literally crying in the parking lot. And she was like, oh, honey, you got to let him go. He's moved on. The whole time knowing it was her. That's that sociopath behavior, right?
LUKE: It sure is. And it's not what you would expect about a friend of 30 years. So my advice to you is just stop talking to that chick. Let her go. Let her have the dude. And, uh, move on with your life.
TERRY: You're right. I know you're right. It's just, God. It's pathetic, but I'm sitting here thinking about who I'm going to call now when stuff goes wrong. You know, like she was that person.
LUKE: Well, it sounds like you're going to call Luke at the Roost, the, uh, call in radio show where we help you out with your real world problems.
TERRY: Ha. Yeah. Well, you're doing better than she did tonight. That's for sure. I just... Okay. Real talk, though. Am I crazy for still being hurt about David?
LUKE: No, you're not crazy.
LUKE: You're hurt when you're hurt, and you've got good reason to be hurt here, and you've got some grief going on, and it's big life changes, and there's lots of reasons to be hurt. But you don't have to continue to stay that way. If you don't want to, you can decide to not be hurt and move on. And I think that's what you need to. to do. It's easier said than done, but it can be done. So just every day, wake up and be grateful to be here and say, hey, you know, I don't have the life that I had yesterday, but I've got the life that I have today, and it can still be pretty fucking cool.
TERRY: You know what? You're right. And honestly, and this is going to sound terrible. But I think I've been more hung up on the idea of David than actual David for a while now. Like we got married at 23, Luke. 23.
LUKE: Yep, that happens. That is young, and it explains a lot. So, um, you know, I wish you the best of luck. We're going to move on on the show now, unless you have some other point to bring up. I'll give you one more chance to respond. But otherwise, I'm going to say, uh, let go with the chick, let go to the dude, move on with your life, and everything's going to be fine.
TERRY: No, you're good. I appreciate you letting me vent. And hey, I heard Roland, earlier? The UPS guy?
LUKE: Yeah, what about him?
TERRY: You got a job for Roland? No, but his wife definitely sucks too. That whole thing about her wanting him out of the house? Come on. That man needs to check the credit card statements.
LUKE: That is absolutely true. I didn't want to say that to him at the time, but he absolutely should check the credit card statements. And maybe the little internal camera that he's got in the fucking teddy bear And now, now folks, you know what? Actually, she's already gone, but Terry, that was the smartest thing you sent that whole call. All right. Now it's time for a word from our sponsors. I'm not saying that for sympathy. I'm saying it because Pillow Forever asked me to establish a before state, and that's mine. Pillow Forever is a memory foam pillow that remembers your head shapes so you don't have to. It's got cooling gel, bamboo fiber, and a 30-night risk-free trial, which means you can sleep on it for a month and then send it back, and someone in a warehouse has to deal with that. Every pillow forever comes to. in a box that's too small, which is part of the experience. You open it and it slowly expands like a major documentary. My dog tried to fight it. Pillow Forever. You deserve better than a horse blanket. Okay, thanks to Pillow Forever. They are a proud sponsor of the Luke at the Roo Show, and we appreciate them. Now let's get to, uh, let's get back to the phones. Chester, Chester, welcome to the show. What's on your mind, sir?
CHESTER: Luke, hey, so I almost killed my kid this morning. Not on purpose, I mean. I didn't. I showed up to get her from her ex-wife's place, custody Sunday, and the second I rolled down the window, I could smell the whiskey coming off me. Hands still shaking. I'd been drinking until like four in the morning, and it was eight o'clock. And I'm sitting there with the engine running, thinking, well, I'm sitting there with the engine running, thinking, well, What the hell am I doing? She's seven. Her name's Daisy.
LUKE: Oh man, yeah. You can't, you can't do that, man. If you've got a drinking problem, then you're going to have to take, you're going to have to take care of that because you could kill your kid and or you could kill someone else's kid and it's really not okay. It's, it's irresponsible and it's not good for anybody.
CHESTER: No, you're right. You're absolutely right. I didn't drive. I mean, I sat there for maybe 30 seconds. And then I shut the truck off and I called her mom and told her I was sick, food poisoning or something, which is bullshit. She probably knew.
LUKE: How long have you known you've had a problem with the drinking?
CHESTER: I mean, that's the thing, Luke. I don't drink every day. I work at Desert Star Pond, been there six years. I show up on time. I do my job. It's just when I get home and Daisy's not there. When it's those empty nights, I'll start with one beer. And then it's three I'm watching Civil War documentaries with a bottle of Jim Beam. And I can tell you the exact date of Antietam, but I can't tell you when it got like this.
LUKE: Well, are you, uh, are you tired of it yet? Or it sounds like maybe this is a moment of clarity where you're recognizing the situation and, and thinking about taking accountability for it. Is that true?
CHESTER: Yeah. Yeah. Yeah. I think. I mean, my dad, Big Jim, he ran cattle for years. Never missed a day. Never touched the stuff.
LUKE: Well, you know, they have support group meetings. It can help you out in these situations. You can go to after work. Lots of cool people there that are trying to live sober, not pick up the drink and end up in situations like you're finding yourself in now. It is possible. You don't have to get drunk by yourself at night watching Civil War documentaries. You can just watch the Civil War documentary without they're getting drunk bit. then when you get up in the morning and pick up your kid, you're not going to be drunk.
CHESTER: I know. You're right. It's just, the house is so damn quiet, you know? And I keep thinking about what kind of example I'm setting.
LUKE: Well, it's good that you figure it out now when you're at a spot where you can still do something about it. And what I recommend is when you get home, next time you get home, do something else. Instead of cracking that beer, just do anything else. You can play a video game or you could, uh, go for a walk or you could read or play on the internet, whatever it is, just break that habit of having that first beer. Because if you don't pick up the first one, you're not going to end up drunk. Isn't it funny how that works? If you don't pick up the first one, you're not going to end up drunk.
CHESTER: Yeah, yeah, that makes sense. I heard Lori earlier, the trucker lady with the daughter sneaking out, and I kept thinking at least she's got a reason she's not around, you know?
LUKE: No, what do you mean by that?
CHESTER: I mean, she's working. She's on the road providing for her kid. Me, I'm just, I'm home and Daisy's at her moms. And I'm choosing to sit there with a bottle instead of, I don't know, calling her reading her a bedtime story over the phone. Something.
LUKE: Well, yeah. And, uh, you know, sometimes you just have to say that to a radio host at midnight to, uh, to hear yourself say it. So hopefully you can snap out of your, uh, your, your situation. Don't pick up the beer next time. And instead, call your daughter and read her a story.
CHESTER: You're right. I actually. I got this book about Gettysburg I was going to show her. She's been asking about it because we drove through Pennsylvania last summer. And she saw the signs. Smart kid. Smarter than me, that's for sure.
LUKE: Well, you sound pretty smart today. You know you get a problem and you're going to do stuff to take care of it. So if you need help, then I recommend that you find your your local neighborhood at AA or NA meetings, depending on what type of situation you're dealing with. And just go check it out. And have an open mind and maybe you find a new way of life. Okay, Dolores. Delores, welcome to the radio show. How can we help?
DELORES: Hey, Luke. Yeah. So, okay. I got recognized today at an open house and I kind of panicked. And now sitting in a circle K parking lot feeling like a complete idiot.
LUKE: Well, all of us get recognized several times a day. So why is it, what is it about this particular recognition that's got you up in arms?
DELORES: Because I've been going to open houses every weekend for like eight months and I'm not actually buying anything. This realtor, Patricia, she remembered me from a showing last spring, and she just asked me you actually planning to buy a house and I froze. I told her my financing fell through, but that's not even true because I've never applied for a loan in the first place.
LUKE: Okay. Next up on the radio show, we have Marvin. Marvin, thanks for calling in. What's happening tonight?
MARVIN: Hey, Luke. Yeah, thanks for taking the call. So I got home for my shift tonight and there's this cardboard box sitting on my porch. No note, nothing. And it's full of stuff from my dad's house. Stuff I haven't seen since he died 11 years ago. His Bolo tie. This pocket knife I thought I lost senior year. Photos of my uncle's old place.
LUKE: What's a Bolo tie?
MARVIN: Oh, it's that Western Thai thing. You know, the braided leather cord with the silver clasp that slides up and down. My dad wore one to every wedding, every funeral, every time he needed to look respectable. His had this. This turquoise stone in the middle, real heavy.
LUKE: Okay, well, that's cool. Somebody dropped off some of your dead dad's belongings, and what are they bringing up inside? They bring it up memories? Is this got you upset? Are you concerned? Like, what's, what feeling is this bringing up inside of you?
MARVIN: I mean, yeah, memories. But more than that, I'm sitting here like, who the hell had this stuff? my dad's been gone 11 years. Where has this box been? And why now? Why tonight? What's in the box? Why no explanation? Like, someone just decided, oh, time for Marvin to deal with this and left it there like a package from Amazon. And honestly, I opened it and just...
LUKE: All right. Well, congratulations for, uh, the... safe return of your dad's belongings. I hope that you wear them in good health. Next on the radio show, oh, before, no, oh, Luke, you almost, come on, come on, I'm fucking amateur here. It is time for a word from our sponsors. Look, I'm not a financial advisor. I'm a guy with a microphone and a dog. But the folks at Crypto know asked me to tell you about their new decentralized investment platform, and I to read this part. Past performance does not guarantee future results. This is not financial advice. And if you invest your rent money, you deserve exactly what happens next. Crypto No lets you trade over 400 digital currencies, including three that were invented this morning, and one that's just a picture of my dog. The app features a real-time portfolio tracker with a built-in panic button that just plays ocean sounds when your balance drops. Crypto No! Fortune favors the bold, but it does not return their calls. Okay, thanks to Crypto No for sponsoring the show today. I think we're probably running a little bit late. These have been some decent calls. They're going a little long. So I'm just going to take one more for tonight's show. And next up to the line, we have Francine. Francine, you're going to be our last caller of the night.
LUKE: What's on your mind?
CALLER: Luke, I just had a deer on the 11, and it's still alive, and I don't know what the hell I'm playing. supposed to do about it. I've been sitting here with my hazards on for like 20 minutes. Just, it's looking at me. When if its legs is completely shattered, it's on the shoulder. And I can't tell if I'm supposed to call someone, or if I'm supposed to, I don't know, put it out of its misery myself? Which I don't even know how I do that. I'm a nurse. I work on people, not my ex would have known. He grew up out here, hunted with his dad. He would have just handled it.
LUKE: Yeah, that sounds like a. less than ideal situation. I would call the police first, call 911, and let them know that there's been an accident and there's an animal, I assume, still in the road. If you have a gun, then I think you know what you have to do. If you don't have a gun, I would probably refrain from doing anything hasty with like a tire iron or a rock.
CALLER: No, I don't have a gun. It's off the road. It's on the shoulder. So nobody's going to hit it. I just, I'll call. I didn't know if this was even a 911 thing or if there was like animal control or something, but out here at midnight on a Sunday, I guess there's not exactly a whole directory of options. The stupid thing is, I've been driving the same loop between Deming and Los Cruces for six months now. Three different clinics, and this is the first time I've hit anything.
LUKE: Well, you know what I always say? 911 was an inside job. So I don't actually know who you're supposed to call in this situation. I would call 911 and ask the them and they'll let you know or send out animal control or whoever comes out of the sheriff's office. Who knows out here especially? But, you know, it happens. There's wild animals out here. They run in the road. Sometimes we hit them. It's probably not your fault unless you were playing with your phone, and in which case it was your fault. And just do the best you can do. It's unfortunate that the animal's suffering right now, but if you don't have a way to humanely end it, then you got to just get somebody out there as soon. as you can.
CALLER: I wasn't on my phone. I was just tired. Just came off a double. Wasn't paying attention like I should have been. You're right, though. I'll call.
LUKE: All right. Well, get off the line with me and get on the line with 911. And that is the conclusion of our show for tonight. Thanks everybody for tuning in, and we'll talk to you again tomorrow. Thank you.
@@ -0,0 +1,397 @@
LUKE: All right, welcome back to the show. My name is Luke. I'm your host for Luke at the Roost. This is the radio show where we take callers and give them the best advice that we can. If you'd like to call into the show, the number is 208-439-5853. That's 208-439 Luke. Also, new just yesterday. If you can't get to a phone and you'd like to participate in the show, you can send us an email. The address is submissions at Lukeat the roost.com. Submissions at Luke at theroost.com. So you can send us a question or a story or anything you want. And if it's funny enough, I might read it on the air in the next show. And if it's not me, maybe I'll have one of my robot friends read it for me because I'm lazy. All right. So today is Monday, February 16th. It's about 11.30 p.m. And our phones are lighting up like they always do at this time of night. the show, we have Hector. Hector, welcome to the show. Thanks for calling in. How are you doing out there tonight?
HECTOR: Hey, Luke. Doing all right, man. Doing all right. Appreciate you taking the call. So listen. I got myself into a situation here, and I don't know whether to laugh or start updating my resume.
LUKE: All right, Hector. What did you do?
HECTOR: So I've been flying crop dusters out of Yuma for eight years now, right? Good job. Decent money? I know what I'm doing up there. This afternoon, my boss pulls me aside. All smiles tells me I landed this huge contract in Mexico City. Three weeks coordinated with agricultural officials. Big deal for the company.
LUKE: Is that something you don't want to do? Why you bummed out about that? That sounds great.
HECTOR: No, no, that's the thing. It should be great, right? This is a promotion, basically. More money, more responsibility. Looks amazing on paper. Here's the problem, Luke. hired me eight years ago partly because my last name is Gonzales and my resume says fluent Spanish I speak about as much Spanish as you probably do maybe less
LUKE: I'm gonna let you in on a little secret I think they knew they probably had strong suspicions at the time they hired you that you did not speak Spanish but that's okay because nowadays you don't have to you can get the Apple AirPods and they'll do the live translation for you right in your ear
HECTOR: Oh man, I didn't even think about that. Does that actually work, though? Like in a real professional setting? Because I'm picturing myself in some meeting with Mexican agriculture officials, trying to negotiate pesticide schedules, while I'm sitting there with AirPods and like I'm listening to a podcast.
LUKE: You know, I went into Mexico last year at some point. I went to Agua Brietta. And when I got there, I was trying to buy cigarettes from the duty-free shop on the United States side of the border. So they don't just give you the cigarettes. You buy the cigarettes in the United States, and then you have to cross the border, and some guy is supposed to bring you the cigarettes, and then you have to import them back into the United States. So I went out there, and I was in Mexico, and I couldn't find the guy with the cigarettes. So I'm asking all around, and there's a bunch of, like, their version of Border Patrol at the border, and they didn't speak any English or pretended not to speak any English, and they pulled out their phone, and they just did the whole back-and-forth transcription, like it was nothing. Like, they do that all the time. So I think it's pretty common. I wouldn't worry too much about it. But you can always start studying. I mean, it's not like you can't learn Spanish.
HECTOR: Yeah, I mean, three weeks, though. I've been downloading Duolingo on and off for like two years, and I still can't get past the boy eats an apple. And here's the other thing that's dealing at me. I've been cashing paychecks for eight years with that my resume like they never needed me to speak Spanish before so it never mattered but now it does and I feel like a fraud
LUKE: well you are a fraud you lied on your resume and you got away with it for eight years so congratulations for that but it sounds like it's probably pretty difficult to find somebody qualified to fly crop dusting plane that they trust in another country and they trust you so you just got to do the best you can and try to work through the Spanish you know Three weeks, that's quick, but you can do it. I mean, dual lingo is probably not the right way, but there's lots of other ways to learn language, and if you really need to, if you really have to, you could get at least a little bit of what you need. It shouldn't take you long to ramp up.
HECTOR: You're right, you're right. I mean, I've been hearing Spanish on the radio stations down here for years. I should at least have some baseline. And honestly, the flying part I can do in my sleep, what's got me twisted up.
LUKE: Just throw yourself on a Selena record, get some bitty bitty bum bomb going, and you're gonna be fine. Amherst yourself in Spanish, and you will pick it up faster than you think, because you have to.
HECTOR: Okay, Selina, I can actually handle. My mom used to play that stuff all the time when I was a kid.
LUKE: You can't handle Selena. Wait, does that make it worse, that I didn't pick up Spanish?
HECTOR: She's probably going to lose it when I tell her about this. She's been asking me for years why I don't speak it and I always just shrugged it off.
LUKE: Well, your name is Hector Gonzales, so I mean I would expect at this point in your life you probably would have picked up a little bit here and there.
HECTOR: That's what kills me, Luke. Everyone just assumes. I walk into a place, they see the name tag, and half the time someone starts talking to me in Spanish, and I just stand there like a deer in headlights. I've gotten really good at the apologetic smile and the sorry I don't speak it. But my dad's of the family, they all came over generations ago. By the time it got to me, it was just gone.
LUKE: Hey, well, congratulations on the new job. Do the best you can. And if they have to send you back to the States to do your old job again, then so be it. And if they fire you for lying on your resume, then get another job. There's probably plenty of places you can commercially fly a plane.
HECTOR: Yeah, you know what? You're right. Worst case scenario, I'm back flying over lettuce fields and Yuma, which honestly isn't the worst thing in the world. I do love that job.
LUKE: Well, good luck to you and congratulations again. Thanks for calling in, Hector, and give us a call back if anything goes wrong or you meet a crazy cartel out there and you've got a story about redemption. Next up on the line we have Marlene. Marlene, welcome to the show. What's going on in your life tonight? How can we help?
MARLENE: Hey, Luke. Yeah. So, My ex-wife's new husband just ordered $12,000 worth of furniture for me, and I don't know if he knows it's me or not, and I'm losing my mind a little bit. Sorry, that came out fast. But yeah, that's what's happening right now.
LUKE: Well, you know, all the places in the world you could buy furniture from, if he's buying it from you, then there's a good chance he has an idea of who you are. Right?
MARLENE: That's what I keep thinking. the company's called Desert Reclaim and my name's all over the website. But then I'm like, maybe he just Googled Rustic Dining Tables, New Mexico. And I came up and he has no clue. He's this tech guy from Scottsdale, you know?
LUKE: Yeah, well, I mean, you got to think about how internet search works and cookies and stuff like that. So there's probably a chance that she was looking at your Facebook profile and somewhere you're linked to your business profile on LinkedIn or something like that. And then that's a chance that's transferring to his phone, so when he does a Google search, it's looking at all that data it's collected on you, and saying he's most likely to buy his chairs from this woman.
MARLENE: Oh, God, I didn't even think about that. So, like, Rachel's been looking at my stuff and it's bleeding over into his search results. That's, actually, that makes me feel weird in a whole different way.
LUKE: Like, has she been checking up on me? Well, I don't know. I'm just saying that's one possibility. That is how search works nowadays. So there's a likelihood that that could have happened. And not necessarily like Googling you in the middle of the night, but there could be documents on our computer that are getting fed into the cloud for some PDF transcription service. Like, you don't know. All that you know is the data is out there and you're linked to him now in a not a cryptographic way, but in a digital way. And that link is obviously working.
MARLENE: Yeah. No, you're right. I mean, we were married for six years. probably all kinds of digital trails still connecting us. But, okay, so whether he knows or not, do I take the money?
LUKE: Yes! Luke, this is six months of my mortgage. This order would let me actually breathe for the first time since I started this business.
MARLENE: But then I think about Rachel walking into one of those hotel lobbies and seeing my tables, the ones I built with wood from that barn outside animas, and I just...
LUKE: Not your problem. Take the money. Congratulations. That's a good hit. That's what we call us in the business. So close that deal, take your cash, and enjoy the win. And thank modern technology.
MARLENE: You really think so? Just take it and don't say anything? Because part of me wants to email him and be like, Hey, just so you know, I used to be married to your wife. Is that insane? That's insane, right?
LUKE: You are correct. That is insane. Yeah, don't do that. Take the money and don't say a word. And who cares if she looks at York when she goes to wherever it is, she goes where they put your furniture.
MARLENE: Okay. Yeah. You're right. I mean, she left me. She married him. I'm building furniture in my garage at two in the morning. This is literally the one good thing that's happened.
LUKE: Nah, she got to be with somebody she wants to be with. You get to make maybe potentially the best work of your life. And you just got a nice paycheck. So it sounds like there's lots of good things happening. And all around, everything works out the way it's supposed to. And enjoy this small win.
MARLENE: Small win? Luke, this is 12 tables. This is the biggest order I've ever had. But, okay. Yeah, I hear you. Take the money, build the tables, stop overthinking it. It's just, man, it's been a year of sleeping in my truck, watching the stars, trying to figure out what I'm doing. And now this lands in my lap and it's connected to her. And I just...
LUKE: You're just freaking out. And you don't have to freak out. Do the job, get the money and move on and enjoy it. Congratulations.
MARLENE: You know what? You're right. I'm going to do it. I'm going to build the best damn tables they've ever seen, take their money, and maybe finally fix the heater in my truck, so I don't have to freeze my ass off out there.
LUKE: Excellent. That sounds like a great plan. And another happy rooster. It's time to hear from our sponsors. So in just a moment, we'll return.
I'm going to be straight with you. I don't know what this product does. The folks at Bunkhouse DNS sent me a script and I've read it four times and I understand maybe 11% of it. Apparently your DNS is exposed, which sounds medical. Your queries are being logged by default, which sounds illegal. And Bunkhouse reroutes your lookups through encrypted tunnels, which sounds like something a villain would say in a movie before the building explodes. What I can't really tell you is this. I installed it. I pressed one button and a little shield icon turned green. Green means good. I know that much. My internet still works. Nothing exploded. And apparently my queries, whatever those are, are now private, which feels like progress, even though I don't fully understand progress from what? Bunkhouse DNS. I don't know what it does, but the shield is green, and that's enough for me.
Okay. Welcome back to the show. Just a quick plug. Don't forget, we have presents out there on all the social media. So you can find us on the Facebook and the Instagram and the YouTube's and the Apples and the Spotify's all around. If you want to look at all the places you can find us, look at Lukeattheroast.com. And with that out of the way, let's get back to the show. Rick, Rick, thanks for calling in. What's going on, buddy?
RICK: Oh man, Luke. So yesterday morning, my daughter Claire, she's 12. She fell through the on our stock tank trying to break it up for the horses, right? Full face pallant into freezing water, and I'm watching this happen, and I'm about to lose my mind. But she comes up and she's gasping, but she's not panicking, like it all. And later she tells me, it felt like her body just...
LUKE: Her body just what?
RICK: Took over. That's exactly what she said. My body took over. Like she wasn't even in control. Her heart slowed down, blood went to her core, and she just knew what to do. So I'm sitting there after she goes to bed and I'm looking this up. And Luke, it's called the mammalian diving reflex.
LUKE: Well, that's pretty helpful, I suppose. Uh, I, for if we like jump into the ocean when it's cold, like, uh, why is it there?
RICK: Right, exactly. It's this ancient thing we all have, like seals and whales. Your face hits cold water and boom, your body just flips this switch without asking permission. Heart rate drops, blood vessels constrict, everything shut down except what keeps you alive.
LUKE: Well, that sounds like a very handy thing to have, especially if you're going to fall into a trough of water when it's frozen.
RICK: Yeah, no. I mean, she's fine. She's totally fine. But Luke, here's the thing that's messing with me. I've known this kid her whole life, right? Change to diapers, taught her to ride, the whole deal.
LUKE: Okay. And what's the problem? It sounds like she's perfectly fine, and you learned a new thing about biology and evolution.
RICK: The problem is, I watch her face in that water. And it was like, I don't know her at all. Like there's this whole other operating system running underneath that I have zero access to. She's got the same emergency programming as a seal, Luke, a seal.
LUKE: I've been kissed by rose on the grave. Yeah, I guess that would be startling. But wait a few more years until you see her get hammered. And then, uh, see what you see in her face.
RICK: Oh, God. Don't even. No, but see, that's different. That's like choices and stupid decisions. This wasn't a choice. This was her body making calls without her even knowing. Like she's running software I didn't install and can't see the code for.
LUKE: Yeah, and it sounds like it's doing a better job than yours too, because she acted superfluously and got herself out of that cold water before it was an issue. So what's the problem here? Her natural biology was taken over and doing the right thing.
RICK: The problem? Okay. You're right. She handled it better than I would have. But Luke, she's 12. And I'm standing there on a Monday morning watching Evolution work through my kid like she's just...
LUKE: Yeah, you watched Evolution the whole, every day of watching that kid grow up, and all of you growing up and all your parents. So I'm not sure why this is flipping your wig.
RICK: Because this wasn't her getting taller or losing teeth or whatever. This was watching her go under and come back up with this look on her face like, Like some part of her I've never met, just took the wheel. She told me later, my body took over dad, and I can't stop thinking about what else is in there that I don't know about.
LUKE: I've been kissed by Rose on the grave. Do you think seal is in there?
RICK: What? No, I.! Luke, come on. I'm not saying she's literally a seal. I'm saying she's got all this programming I didn't know about. Like, what else kicks in that I'm not ready for? What other switch is flea when I'm not looking.
LUKE: I don't know, man. It sounds like you're going to have to read the origin of species and do some research on the old Charlie D.
RICK: Okay, you're busting my balls here, but I did look it up after she went to bed. The mammalian diving reflex. It's real.
LUKE: Heart rate drops. Blood goes to the vital organs, the whole thing. And she described it exactly like that without knowing what it was called. That's what's freaking me out. She's walking around with all these systems I can't see. And one day she's going to be out there making decisions. and I won't even know what's running in the background.
RICK: Yeah, that's true. That's how humans behave. Welcome to the world, sir. You don't know how anybody is programmed entirely, and just the little bits that you've been around for. So it's not to be shocking, but it sounds like you need to expand your mind a little bit. Your kid is going to be their own person with their own instincts and values and beliefs and skills. And most of them. them, not all, most of them will have absolutely nothing to do with you.
LUKE: Yeah, yeah, I know that. I do, but this felt different. Watching her face change in that water, watching something ancient, just click on.
RICK: Well, it sounds like everything worked out, and she's great, and you learned something, and that's a win to me.
LUKE: So thanks for Colin. Give us a call back some other time. Gina, Gina, thanks for Colin. How are you today?
GINA: Hey, Luke. I'm all right. So my brother and sister want to sell my grandmother's house, and I've been breaking into it every Sunday for the last three months.
LUKE: Is your grandmother still in the house?
GINA: No. She died in November. I have a key, though, so I guess it's not really breaking in. But they don't know I'm going there.
LUKE: Okay, why are you breaking into your dead grandma's house?
GINA: I don't know, man. I just, I drive down to Hachita every Sunday, let myself in, and I sit in her chair by the window for exactly two hours. Sometimes I'm on my phone. Sometimes I just watch the mountains. It's this floral armchair she'd always sit in.
LUKE: Well, that's a nice thing, but if they're going to sell the house, you're not going to be able to go there anymore. So that's a bummer. What are you going to do after the house is gone?
GINA: That's the thing. I don't want them to sell it. My brother left me three voicemails saying we need to list it by March. And my sister sent me the Zillow estimate and I had to pull over on the highway because I couldn't breathe. Like, I know how this sounds.
LUKE: Do you have the money to buy the house?
GINA: No. I work at a mortuary. I'm 21. I don't have that kind of money. But I can't explain this to them without sounding crazy.
LUKE: Well, you're 21. You also don't have the bills yet that they have, and the responsibilities financially that they have to account for. You don't have to pay the taxes on that property and the upkeep and the maintenance. You're just going there and hanging out, which is great. I mean, if you've got a free place to go hang out, wonderful. But the rest of your family, doesn't feel that way about it. It's a burden to them, and it's expensive, and they might not be able to afford it either. So if they want to sell the house, then you should let them.
GINA: I know all that. I get it. But she's still in there, Luke. And I know the difference between a body and a person. I work with dead people every day.
LUKE: Okay. Well, so if she's still in there, does it matter who owns her?
GINA: What do you mean does it matter who owns her? Some family from Phoenix is going to buy it and go. that the whole thing and turn it into an Airbnb or whatever.
LUKE: Yeah, and that sounds like an ideal situation for your dead grandma, because she's going to be hanging out in the Airbnb, it's going to be all nice and fixed up, and people are going to be partying in there, she's going to be having a grand old time.
GINA: That's not funny.
LUKE: What do you mean it's not funny? Of course it is.
GINA: No, because, okay, you don't get it. Every Sunday I sit in her chair by the window and I can feel her there. feel her. The way the light hits the mountains at a certain time, that's what she looked at every day for 40 years. Her handwriting is still on the grocery list on the fridge. There's a water stain on the ceiling from when the swap cooler leaked in 2019. And she never fixed it because she said it looked like a bird.
LUKE: Yeah, it sounds like she was a person and she is not anymore. And her house is no longer belongs to her and you're in it hanging out. And the family needs to sell that house, whether you want to be there or not. Like, you can't just force them to keep this house. You can't do it. It's not going to work. So you're going to have to come to terms with reality.
GINA: I know I can't force them. That's why I'm calling you instead of just, I don't know what I thought you were going to say.
LUKE: Well, what I'm going to say is it makes sense that you're grieving for your grandmother. And her presence is obviously. going to be in the place where she lived her whole life. That makes perfect sense. That happens to everybody. I'm going to tell you that you can take those memories of your grandma somewhere else, and you're going to have to. So it's okay to grieve. It's okay to miss her, but she's not in the walls. She's not in the stain on the ceiling. She will follow you for your life in your mind, in your memories, and keep those good, you know? And if you really, if you really, if you really feel that strongly about it? Do what you have to do to raise the money to buy the house. Like it's an after-TV special, and there's some creepy old corporation that's trying to buy it and turn it into an oil drill.
GINA: I make $22 an hour at the mortuary, Luke. My brother's a project manager in Tucson, and my sister works for the state. They've already run the numbers. If we sell now, we each get like $40,000. That's real money for them. That's their kids' college funds or whatever. And I can't explain to them why it matters without sounding insane.
LUKE: It doesn't sound insane. It sounds selfish. Because it's not yours to hold on to. So if you make $22 an hour at the mortuary, that's not a lot of money, but you could go make more money. You could have a second job, a third job. You could make deals with people, talk to the bank. $40,000 is not that much money, and you could raise it if you really wanted to. You can do a GoFundMe. I mean, you can crowdsource a lot of money. You can borrow a lot of money. You could make it happen, but, I mean, it goes back to you. And this would be something that you have to do. It's not something that you're forcing those other people that are in real world responsible situations. You can't be upset with them for doing what they need to do.
GINA: You're right. God, you're right, and I hate it. I've been sitting there every Sunday like I'm keeping her alive or something. Like if I'm keeping her alive or something. I just stay in that chair long enough. It won't matter that she's gone. But I work with dead people every day, Luke. I know better. I'd prep buddies.
LUKE: Well, if you know better, then snap out of it, because she is gone, and she's not in the house, and it's not your house.
GINA: Yeah. Okay. Yeah. You're right. I just, I have to figure out what to say to my brother, because he's been really patient, and I've been dodging his calls for two weeks. I can't keep doing that.
LUKE: Well, figure out what you're going to say to your brother, and make peace with what you've got to make with or go get yourself the money.
GINA: I'm not getting the money. I know that. I think I knew that when I called.
LUKE: Okay, you're not getting the money. Well, good luck to you, you little bitch. Jesus. I don't know. What generation is that? She said she was 21. Is that Gen. A, Gen. Gen. I don't know what gender. She sucked. Okay. Let's see. Skeeter. Welcome to the show. What did you think of that last caller?
SKEETER: I thought she was super annoying. Oh man, I missed it actually. I've been sitting out here in the parking lot with the heat running, just got the station dialed in. But listen, I got a question that's been eating at me all day.
LUKE: You know that thing about how orange used to be called yellow red? Like before the word orange existed, people just called it yellow red? I did not know that, and that sounds odd. It sounds untrue.
GIO: See, that's what this kid in my sense. seventh period said, called it bullshit internet fake history, right to my face, which, you know, fair enough, I let them talk like that in my class, as long as they're making a point.
LUKE: And did you have anything to back this up with?
GIO: That's the thing. I don't know, man. I saw it somewhere online, but it was interesting, been telling people for like two years. And now I'm sitting out here at midnight, second-guessing, whether I actually know anything real, or if my whole brain is just filled with internet garbage, Like, what else have I been confidently wrong about? You know what I mean?
LUKE: Well, have you taken any steps to verify the information? Like maybe Google it, look it up?
GIO: Yeah. I did that after class, and it just made it worse. Half the sites say it's true, half say it's not, and I can't tell which ones are legit. There's something about the word coming from the fruit, or maybe Sanskrit. Or maybe it was Geo-Lou Reed in Old English, which I guess means yellow-red? I don't know. I teach history. and I can't even figure out the history of a color. And honestly, Luke, that's not even really why I'm calling.
LUKE: Well, Gio, Lou Reed, sounds like something I want to get behind. All right, why are you calling?
GIO: I've been sleeping in my truck in the school parking lot for three nights because my girlfriend changed the locks on me. Found some texts I sent to someone I shouldn't have been texting. And I'm sitting out here with the engine running so I don't freeze, watching the frost build up on the windshield. And I'm the exact same age. my dad was when he left.
LUKE: Okay, well, it sounds like you're getting what you deserve. So who are you texting? What was that about?
GIO: Her name's Amanda. She's a substitute teacher, comes through maybe once a month, and it wasn't even, look, nothing actually happened. It was just flirty stuff, stupid stuff, but enough that when my girlfriend saw it, she knew exactly what I was thinking about doing, even if I hadn't done it yet. And the worst part?
LUKE: What's the worst part? You know women don't like. it when you text other women.
GIO: The worst part is, I don't even feel that bad about it. Like, I know I'm supposed to be devastated, right? Begging to come back inside, flowers and apologies and all that. But mostly, I just feel nothing. I've been with Sarah for four years, and when she changed those locks, part of me was relieved.
LUKE: All right, well then, congratulations. It sounds like your decision's been made for you. You're all set. You're free, sir. You're a free bird. You're a free agent. You can text whoever you want.
GIO: Yeah, except I'm 37 years old sleeping in a Tacoma in a high school parking lot. Real winter energy, Luke. And here's the thing that's messing with me. My dad did this exact same thing. Same age? Found someone else. Just walked away from everything. I spent my whole life thinking he was a piece of shit for that, and now I'm out here doing the same math he probably did.
LUKE: Well, you're not really just walking away from everything. She changed the locks on you. She told you to leave and apparently not come back.
GIO: No, you're right, but I handed her the reason to do it. I mean, come on, Luke. I left those texts where she could find them. My phone was sitting right there on the counter while I was in the shower. I'm not stupid. Part of me wanted her to see them. Part of me wanted her to be the one to pull the trigger so I wouldn't have to.
LUKE: Okay, well then, you're a pussy, and it sounds like you've got exactly what you wanted. So congratulations. I'm not sure why you're calling me.
GIO: Because today, I told my seventh period class, that thing about how orange used to be called yellow-red. And this smart-ass kid, always correcting me, said that's fake internet bullshit. And I'm sitting out here now wondering if he's right. Wondering if I actually know anything real, or if I'm just repeating stuff I saw online at three in the morning. And that feels like, I don't know, man. It feels like everything right now. Like maybe I don't actually know what I want or what I'm doing or who I even am.
LUKE: Well, welcome to the goddamn human race, sir, because nobody really does. It's the big secret that we're all hiding and pretending, you know, telling the kids that isn't the case. Nobody fucking knows, man. Just do your best. If you don't want to be with that woman, don't be with the woman. And it sounds like you're not anymore, so good on you. Now you're going to have to find yourself a new place of residence. So I would be worried about that more so than the color orange.
GIO: You're right. You're absolutely right. I just... Okay, so earlier you had that guy Hector on, the crop duster who lied about speaking Spanish, and you told him, Worst case, he just goes back to doing what he was doing before, which he loves anyway.
LUKE: Yeah, I was talking about a job, not a woman.
GIO: I know, but that's what I'm saying. I can't go back. There's no version where I go back to what I was doing before, because I don't even remember what that was. Sarah and I got together when I was 33. Before that, I was just, I don't know, Luke. I was just floating around. And now I'm floating around again, except I'm four years older and sleeping in my truck, and I can't even confidently tell a 17-year-old kid whether Orange is a real cool. color or not.
LUKE: Yeah, you're a goddamn mess, and you need to fucking pick yourself up and get it together because, uh, this is pathetic.
GIO: Yeah, yeah, you're right. It is pathetic.
LUKE: Okay, so grow up, figure out what you want to do and stop, uh, uh, holding people hostage. Now it's time for another word from our sponsors. I'm going to get dark for a second. You're going to die someday. Not today, probably, but someday. And when you do, what's going to represent your business? A Craigslist post from 2020? A Google Maps pin that still says permanently closed because you forgot to update it after COVID. A nephew who knows computers but actually just has a Reddit account? Your legacy deserves better than a nephew with a Reddit account. Squarehole lets you build something that outlasts you, something your kids can point to and say, Dad built Dad. They won't know it took you 11 minutes. That's between you and Squarehole. And when your nephew says, I could have built that for you for free, you can look them in the eye and say, then why didn't you, Tyler? I asked you in 2021 and you said you were busy and you were not busy. Squarehole. Build it before Tyler has to, because Tyler is not going to. Tyler's not going to do it, so you should talk to our sponsor there. Get yourself a website. A website like our website, Luke at the roost.com. which is not hosted on Squarehole. It's hosted a completely different way. And if you'd like to know how it's hosted, you can take a look at Luke at theroost.com slash how it works. Or we explain how this whole show works to those of you that might be interested. Okay, next up on the line, we've got a returning caller. I remember you. This is Brenda. Brenda. Brenda, you're the ambulance driver, right?
BRENDA: Yeah, that's me. Hey, Luke. So I did it. I actually went on a date.
LUKE: Hey, all right. Good job. Good for you. How'd that go?
BRENDA: Oh, man. It was. Okay. So I met this guy at the grocery store two weeks ago. Right? We were both reaching for the same bag of those frozen tequitos, and he made some joke about it. We got to talking. He seemed normal. Nice smile. Worked in H-FAC. Had his shit together. So we go out Saturday night. He picks this Italian place over on Glendale. And Luke, I swear to God, halfway through dinner, he started. telling me about his for-rating women he's dated. Like a whole spreadsheet.
LUKE: Yeah, I'm familiar. It's the universal, uh, hot, crazy matrix. We're all familiar with that spreadsheet.
LUKE: What, what was it about it that got under your nerves?
BRENDA: No, but seriously, it wasn't just that he, it. He pulled it up on his phone at the table and started walking me through it, like showing me the columns, physical attraction, emotional stability, long-term potential, and then he's like, don't worry, I don't fill it out until after the third date. And he said it like that was supposed to be. Like I'm supposed to be grateful I get two more dinners before I become a data point. And the thing is, I sat there thinking, is this what dating is now?
LUKE: Hey, you know, a lot of women would find that very attractive. I bet he's real good at doing taxes. There's probably a lot of good qualities this gentleman has. He seems very organized and he knows what he wants, and that shows extreme confidence. So I wouldn't be too butt-hurt about it. That's a funny thing. And, I mean, you could go on another date with him or not, but... How are you going to blame the guy?
BRENDA: You know what, you're right, and that's what's messing with me. Because Denise said the same thing at work yesterday. She was like, at least he's honest. At least he knows what he wants. And I'm sitting there going, so why do I feel like I got interviewed for a job I didn't apply for? Like the whole night I'm wondering what score I'm getting. And here's the thing. I don't even know if I liked him, Luke. I have no idea. I was so busy being aware of being evaluated that I didn't evaluate anything myself.
LUKE: That's exactly, that is precisely what a date is. It's an evaluation between two people. And whether or not they're saying that there's an evaluation happening, there is. I would be grateful to him for showing you the criteria instead of making it up in his head and thinking you were crazy and scoring you on things you didn't even know he was looking at. So everybody's doing that consciously or subconsciously, and he's just doing it in a very organized and respectable way.
BRENDA: Okay. No, but seriously, you're actually making me feel worse because you're right. I know. Like, I'm doing the same thing. I went home and told Monica every single detail about him, and we basically did the exact same evaluation. We just didn't write it down. We sat there for an hour going, okay, but did you notice how he treated the waiter? What kind of car does he drive? Does he talk about his ex too much? It's the same spreadsheet. I just pretended mine was more organic or something. And now I'm spiraling because he texted me yesterday asking about a second date. And I haven't answered because I'm sitting here judging him for doing out loud what I'm doing in my head. You know what I mean.
LUKE: Yep. That's a Sounds like the type of nonsensical nonsense that comes in the dating world. Congratulations. Are you going to go on the date with him or not?
BRENDA: I don't know. That's why I called. Because here's the other part. When he texted, he said, I had a really good time. I think you're someone I'd like to get to know better. And it was sweet. It was normal. And I'm sitting there staring at my phone thinking, but what did I score? Like now, I want to know what he wrote down about me. Did I get high marks? Where did I lose points?
LUKE: We'll ask him. Make a game out of it. See which areas he feels you need improvement and see if that's something you're willing to work on, if you agree with him or not, and have a discussion. I think that could be fun.
BRENDA: Oh, God, you're serious. You want me to actually ask him to show me my scores. No, but seriously, though, that's kind of...
LUKE: Of course. He wouldn't have showed you the spreadsheet if he wasn't willing to show you the scores.
BRENDA: Okay, you know what? That's a good point. He literally showed it to me at dinner. He's not hiding it.
LUKE: He's not hiding it. And you're doing the same thing. So maybe write down, write down yours. Put it on paper and show you can compare. You can trade and see where you match.
BRENDA: Oh man. This is either the weirdest advice or the best advice. Monica's going to lose her mind when I tell her this. But actually, no, but seriously. If I'm being honest, the stuff I care about is probably stupid too. Like I literally docked him points in my head because his shoes were too shiny.
LUKE: Well, you sound like a real catch. Good for you, Brenda. Go out on another date with a guy if you liked him. If you didn't like him, don't go on a date with him. But don't judge him too badly for judging you at an event that is entirely derived to judge each other.
BRENDA: You're right. God, you're totally right. Okay, I'm going to text him back yes. And maybe I'll ask him about the spreadsheet on date too. See if he's got a sense of humor about it.
LUKE: There you go. That's the right answer. That's what you should do. I hope you have a good time. And I'm interested to hear what you get on the spreadsheet. So next up on the line, we've got Clyde. Clyde, welcome to the show. What's going on tonight? How are you?
CLYDE: Hey, Luke? Yeah, I'm standing in my garage right now. Because my 23-year-old son just moved back in about six hours ago. And I don't know how to tell him no. third time. Third time he's shown up with his car packed full of everything he owns, saying he needs to figure things out. And he's already asleep in his old bedroom and my ex? Well, we're not really exes. It's complicated. She already texted me saying she's not doing this again. And I'm out here with my half-finished cabinets at midnight trying to figure out how you're supposed to kick your own kid out when he's already unpacked.
LUKE: Okay, we already talked about this. You've got to tell your kid, uh, no, you can't do this anymore. Stop calling the radio show, grow a pair, and talk to your goddamn child. Rita, welcome to the show. How are you?
RITA: Hey, Luke. I'm, yeah, I'm okay. Long night at work, couldn't sleep. I just, I took a call tonight. Welfare check on an old man out in Somerton, and his grandson found him dead. Been there three days.
LUKE: That's no good. Hey, what happens after the body is found. Is there, are there death people that are called to come and clean up the situation? Who's responsible to go in there and take care of the body? And not just the body, but the house?
RITA: Yeah, so we dispatch fire and medical first. They confirm he's gone. Then the coroner comes out. After that, if there's no foul play, family's responsible for cleanup. But here's the thing. This kid, the grandson, he's looking around while we're waiting, and he finds this album. Turns out his grandfather was this Corito singer back in the 70s.
LUKE: Okay. Good for him?
RITA: No, but that's the thing. The kid had. His own grandfather. Played all over the valley, VFW halls, weddings, recorded an album. And now he's just...
LUKE: Hey, if the coroner hasn't been there yet, then how do you know he's been dead for three days?
RITA: Oh, the paramedics confirmed that when they got there, they can tell by... I mean, I won't get into the details. But yeah, it was three days. But Luke, what I'm trying to say is, this guy had this whole life making music. And it just disappeared. Like it never happened. And I keep thinking about how many people out here are like that. Musicians, artists, whatever. They pour everything into it and then they're just gone. Nobody remembers.
LUKE: Yeah, that's true. And nobody recognizes while they're alive either. So there's a lot of people in the world. It's a big planet full of people with different talents. And it's hard to pay attention to all of them, especially when we've got so much crack attention seeking dopamine bullshit on the computers now and our phones. And everybody's listening to some nonsense, like, I don't know, listening to a radio show where the host takes colors from fake entities that are programmed in a computer.
RITA: Yeah, well, I'm listening to that show right now. So what does that say about me? But seriously, I heard you earlier with Marlene. And I think you were kind of hard on her.
LUKE: Yeah, I get that a lot. I think people just don't like to be told the truth.
LUKE: And I'm not really in the business of of coddling people or telling them what they want to hear. I'm in the business of giving the best advice I can. And sometimes that's hard.
CALLER: No, I get that. I do. But what I'm saying is, this old man tonight, he probably never got recognized while he was alive. either. He was just playing his caritos at Quincerra's and nobody paid attention. And now he's gone and his own grandson didn't even know. And I'm sitting here at three in the morning thinking, what's the point?
LUKE: Well, the point is that the dead grandfather had a great time expressing himself musically while he was alive. That was the point. And anything other than that, any recognition afterwards or fond memories of somebody else's past skill is pretty irrelevant in the whole scheme of the universe.
CALLER: Yeah, okay. But then why do we keep anything? Why do we, I mean, I've got this whole box of my dad's old tools in my garage that I never use. Mark keeps saying we should sell them, but I can't. And it's not even about the tools. It's about, I don't know. I don't know why we keep things.
LUKE: I don't. to me and if it's both useful and has, what's the intrinsic value, sentimental value? If it has sentimental value to me and it's useful, I'll keep it. But I don't keep stuff just to keep reminders of people or things or events or places or anything. That's just wasting space.
CALLER: So you think I should just let it all go? The old man's music, my dad's tools, all of it, just
LUKE: What? Make peace with the fact that nothing lasts. Nothing lasts and nothing is supposed to last. And that's how it's designed and that's the beauty of this whole thing we're doing. So, yeah, if you like the old man's music, if caritos are your thing and you enjoy listening to the music, then keep that. If you're going to use your dad's tools, if they're good tools, then keep those. But if you're just holding on to them because of the memory of who they used to belong to, you're wasting space. And every time you move for the rest of your life, you've got to move. a big box of tools or get somebody else to and pay to store it. And it's just, it's unnecessary. It's baggage. So, um, I for one, believe you should travel with as little baggage as possible.
CALLER: God, that's cold. It's not. I mean, you're not wrong. But, okay, so what about the grandson? He's got this one album his grandfather made, probably sitting in some storage unit somewhere. Should he just toss it?
LUKE: Depends on if he likes the record or not. If he's to listen to it, then no, he should hang on to it and listen to it throughout his life. If he doesn't like caritos and he doesn't have a record player, then I don't know that there's a good reason to hold on to that forever.
CALLER: You know what? I actually called about something else. But now I'm thinking maybe it's all connected. I've been keeping a list for years.
LUKE: Okay, a list of what?
CALLER: Musicians, local ones, people who played around here, and then just disappeared. I started it maybe five years ago after I dispatched a call to this house. And when the paramedics got there, they found all these old posters on the wall. Guy had played guitar in like 15 different bands in the 80s and 90s.
LUKE: Well, that's pretty cool. What are you going to do with the list?
CALLER: That's the thing. I don't know. I just keep adding to it. Tonight, after that call with the grandfather, I added him. Miguel Sandoval. One album, 1974. And I'm sitting here at like two in the morning with 40 names.
LUKE: And I'm thinking, well, I mean, that's a, it's obviously important to you. Music obviously speaks to you on a different level, and you also like archiving and keeping the memory of those things going. So maybe this, this makes you suited to run like a record shop or an antique shop where you can hold on to those things and trade them.
CALLER: Wait, hold on. You think I should quit dispatch and open a record shop at 53 years old?
LUKE: Why not? If you love records, you love music, that's what you want to do. You're 53 years old. You can do whatever you goddamn please. And you don't have to quit your job. You could do it on the side. You could do it as a little side hobby, hustle.
CALLER: The archiver. I mean, Mark would lose his mind. We've got the house payment, his trucks on its last legs. And honestly, I don't even collect records myself. I've got maybe 20 at home. It's not about owning them. It's about, I don't know how to explain this. I don't know what you're asking. I guess I'm asking if it matters.
LUKE: No.
CALLER: If I write all these names down, if I remember them, does it actually do anything? Because tonight, looking at that old man's grandson, this kid didn't even know his grandfather made music. Didn't know there was this whole part of him. And I'm thinking, I know.
LUKE: You do know. And that's a piece of information that you can have and not everybody has to have all the same bits of information that you have. No, it doesn't matter. It doesn't matter at all. all. It doesn't matter to anybody.
CALLER: Yeah, you're right. That's what I figured. It's just... Okay. Earlier tonight, Hector called in about lying on his resume, and I was laughing so hard I almost ran. And I thought, that matters right now to him. But in five years, nobody's going to remember that conversation. And Miguel Sandoval, nobody remembers his album either.
LUKE: Nope. I also have, uh, several albums. that nobody ever listened to and nobody will ever remember. And that's just part of being a musician. What was cool about them, though, was creating the albums. And having that in my belt of things that I've done, I'm not everybody can say they've created a whole record of their own music. And I can. And that's a pretty cool thing. So it's worth doing whether anybody listened to it or not. I still listen to it sometimes. That's all that matters. Okay, we're going to take one more call tonight. One more call. Who's it going to be? Who's it going to be? Should it be? Should it be? Chip or Pam? Chip or Pam? Chip or Pam? I'm going with Pam because I've been watching a lot of office clips lately and I forgot how good Pam was on that. All right. So, Pam, welcome to the show.
PAM: Oh man, Luke. Thanks for taking my call. So, okay. Here's the thing. I've been a bail enforcement agent for six years. Tracking down bail jumpers. all over Hidalgo County. And my boss just told me there's an insurance audit coming next week, which would be totally fine except I lied on my resume when I got hired.
LUKE: Oh, what's with all you resume liars? I mean, how is this such an issue today? What's going on? Are you all talking to each other? What's the deal? What was your lie?
PAM: Wait, there's more of us tonight? Oh, God, that's okay. That actually makes me feel a little better. and a lot worse at the same time. I told them I had an associate's degree in criminal justice.
LUKE: Well, you shouldn't have done that. And if they're going to give you hard time about it, then you should get your associate's degree in criminal justice. But I say, let them know that you lied, or don't. That's what I say. Don't say. Don't say anything. And if they find it in the audit, then come clean that you lied. And see if you can escape by. But we're not going to keep this conversation going too long because we already had one with the same.
PAM: Same issue, Pam. No, no, no, wait. Luke, it's not the same thing. The other guy. Hector, right? He can learn Spanish in three weeks. Or fake it with an app. Or whatever. But I've been doing this job for. I've brought in over 200 skips.
LUKE: Was Hector the guy that they pushed out of the plane and Scarface? Was that Hector?
PAM: What? No. That was, yeah.
LUKE: Different Hector.
PAM: But Luke, listen. I'm sitting in my truck right now outside the office with the engine running because I don't know if I should walk in tomorrow or just drive until I hit the golf.
LUKE: That's a bit dramatic.
PAM: Really good. I know how to read people. I know the county. I know which cousins are lying and which bartenders will actually call me back.
LUKE: Okay. Well, good for you.
LUKE: So you're good at your job or you think you are anyway and you want us to think you are too.
CALLER: And you lied on your resume and you may get caught, but you also might not.
LUKE: So just go to work like it's a normal day. It's unlikely that they care. If you're good at you, if you're as good at this as you say you are, then they'll find a reason to leave you alone.
CALLER: You really think so? Because I've been sitting here for an hour just I keep thinking about John Wayne, which is stupid. But he never pretended to be something he wasn't, you know. He just was who he was, and people either liked it or they didn't.
LUKE: I don't know if that's true at all. I bet you John Wayne pretended to be a lot of things. He was not.
CALLER: Well, yeah. I mean, he was an actor, but...
LUKE: He sure was. Okay, bad example.
CALLER: There we go. I just mean, like, the characters he played, they have this code, right? They didn't lie about who they were. And here I am, six years in. And the whole thing's built on this one stupid lie I told because I was desperate and broke after my second divorce. And I thought, how hard can it be to track down people who don't want to be found?
LUKE: Well, I wouldn't worry about it too much. And if it's a problem, then deal with it then. But otherwise, don't lie anymore. You've learned your lesson. No more lies.
CALLER: Yeah. Yeah. You're right. I just, God, I've been spiraling out here.
LUKE: Well, hopefully you feel a little bit better now. Go to work in the morning. Like nothing happened, and just deal with it as it comes. Thanks for calling. And thanks for tuning in to another episode of Luke at the Roost. We hope you have a lovely evening. And we'll talk to you tomorrow. Bye.
CALLER: Thank you.
@@ -0,0 +1,403 @@
LUKE: All right, welcome back to the show. I am Luke, and this is Luke at the Roost. This is the call-in radio show where you can call in, and I'll give you the best advice I possibly can. It's a little bit late today. I had to take a little nap and then push the show back. So today is actually February the 18th, but we're going to pretend. We're going to pretend that it's February the 17th. Why are we going to pretend that? As you may know is Fat Tuesday. That's Fat Tuesday. It's Mardi Gras time, folks, out here in New Mexico. So for today's show, I've got some New Orleans trivia for our callers. That's going to be fun. If you'd like to call in, the number is 208-439-583. That's 208-439 Luke. Or you can also send us an email there to submissions at Luke at theroost.com. So with all that being said, I think it's time to get to our show. Our phones are lit up like they are around this time. And first up, we have Rodney. Rodney, welcome to the show. How are you? Happy Fat Tuesday.
RODNEY: Fat Tuesday was yesterday, Luke.
LUKE: I know. I appreciate the sentiment.
RODNEY: So listen. I'm sitting in my truck outside a closed Chinese restaurant in Lordsburg, New Mexico at midnight. And I just cracked open a fortune cookie that says it's too late. That's it. That's the whole fortune. And I cannot decide if the universe is talking to me, or if I'm just losing my mind in a parking lot.
LUKE: Sounds to me, sir, like you're just losing your mind in a parking lot, because it's never too late for anything. You can be anything you want to be. Wake up in the morning, it's a whole new day, you're a whole new you, and there's nothing you can't do if you put your mind to it. That's what I always say. But we're pretending. We're pretending that it's still fat Tuesday, sir. So go with me. that it is still February 17th, Fat Tuesday. And I've got some trivia for you. Are you ready for that?
RODNEY: Oh, I can do trivia. Hit me. I got nowhere to be. The restaurant's closed. My truck's running. And apparently the universe already gave up on me. So, let's do some trivia.
LUKE: All right, here we go. Why are New Orleans cemeteries called Cities of the Dead?
RODNEY: Oh man. Is it because they bury people above ground down there? Because the water table's so high you'd just be floating your loved ones away if you put them in the ground.
LUKE: That is correct, sir. I've actually been there. It's really weird. They're all in like tombs above ground. It's a pretty freaky situation. Have you ever been to New Orleans, sir?
RODNEY: I have not. Closest I've gotten is Beaumont. Texas, which is, I mean it's humid and it smells weird, but I don't think it counts. New Orleans solar list, though, always has been. Although at the rate I'm going, sitting in parking lots reading ominous fortune cookies, maybe I should just stay in the desert where things make sense.
LUKE: Yeah, maybe you should. That's what I'm doing. Things do make sense here in the desert, and we bury people under the ground here, which is, which makes sense to me, I guess. So, you're sitting in the parking lot, you got your bad fortune cookie, and you're afraid it's too late. What do you think it might be too late for?
RODNEY: My ex-wife texted me three days ago. First time in two years. Just asked if we could talk. And I haven't answered her. And then I crack open that cookie, and it says, it's too late. And I just, I don't know, man. I've been sitting here for 40 minutes now, trying to figure out out if that's the universe telling me I already blew it by waiting three days, or if it's telling me something bigger.
LUKE: I don't think it's I honestly don't think it's telling you anything. I think you just got a fortune cookie at a shut down Chinese restaurant in Lourdesburg, New Mexico, and you can never trust the Chinese restaurant in the desert. So I think I wouldn't put too much stock into what the universe is trying to tell you with your fortune. Instead, I might, if you want to talk to your ex-wife, call her. And if you don't want to talk to her, don't call her. It's that simple.
RODNEY: See, that's what I'm hoping, Luke. That's exactly what I'm hoping. Because I'm supposed to head back to the rig tomorrow morning, and I got to know if I'm just losing it out here. Last year, I killed a rattlesnake in my kitchen with a shovel at three in the morning.
LUKE: Jesus.
RODNEY: Didn't flinch, didn't think twice. This little strip of paper has me calling a radio show. ridiculous.
LUKE: Well, you know what I always say. This radio show is like killing a rattlesnake with a shovel.
RODNEY: Ha, I mean you're not wrong. You just, you just pick up the phone and you do it. You don't think about it too hard.
LUKE: That's right, sir, and that's my advice for you. So if you'd like to talk to the wife, you want to get back, do you want to get back with her? Is that what's going on? Do you miss the X?
RODNEY: Yeah, yeah, I do. I'm not going to sit here in a parking lot at midnight and lie to you about it. I miss her, but here's the thing. She left.
LUKE: Well, she's obviously got something to say to you because she's reaching out. So if you miss her and you want to talk to her and you don't hate her guts, then give her a call and work it out.
RODNEY: I know. I know you're right. And it's not it's not that I hate her. I don't hate her at all. It's that I don't know what she to say.
LUKE: Well, you're never going to know until you make the call, so let's get to that.
RODNEY: You're right. That's, yeah, that's just the truth right there. I've been sitting in this truck for 40 minutes working up to calling a radio show instead of just calling Denise. And that probably tells you everything you need to know about me.
LUKE: That's correct. It does. And we wish you the best of luck, sir. Enjoy your fortune cookie. And good luck with the X. Estelle. Stell, welcome to the show. Thanks for calling in. Happy Fat Tuesday. I know it's not really Fat Tuesday, but we're pretending because I feel asleep. And, you know, I'm a person too. Very, very busy man. Are you ready to play Fat Tuesday trivia?
ESTELLE: Ha. Okay. I did not call in for trivia, but I'll take it. Hit me.
LUKE: Louisiana is the only state that doesn't have counties. What does Louisiana have instead?
ESTELLE: Perishes? That one I actually know. I'm a social worker, so yeah.
LUKE: Very good. That's the correct answer. And what are you calling in for tonight, Estelle?
ESTELLE: Okay, so I came inside at two in the morning and realized I have not had a real conversation about my own life with another human being, and I don't even know how long.
LUKE: Okay, so what is it that you'd like to say to another human being?
ESTELLE: I don't even know where to start. I mean, I know, but it's embarrassing. So here's the headline. I've been having more meaningful conversations with an 87-year-old woman in Hachita about her late husband's dentures than I have with anyone about my own life in, what, six months?
LUKE: Well, you know what, that conversation with that 87-year-old woman about those dentures, that is your life. That's your life, too. So you are having conversations about your life. What is it that's on your mind?
ESTELLE: Okay, yeah, fair. That's, yeah. What's on my mind is that I came inside to, and there was a frozen lasagna on the table that I have microwaved every single Wednesday for six months. And the chair was pulled out at the exact same angle it always is. And I just stood there and thought, did I choose this? Or did it just kind of happen to me?
LUKE: Well, the answer is you chose it because you choose everything. So nothing just happens. If you have lulled yourself into a state of complacency with your life, then change it. But it's something that you did.
ESTELLE: Oh, come on. That's a lot. That's not. Okay, no. I hear you. But it's not that simple. It is. I, my job. I like the quiet. I like the stars. But then I'm out there in the dark with my telescope, and I'm talking to myself about Andromeda, like some kind of, like some kind of desert hermit. And I don't even know if I'm lonely or if I just got used to the silence.
LUKE: So what's the problem? Why is that a problem? Are you enjoying yourself out under the stars looking through your telescope and Andromeda? Because that's what I do. And I am a desert hermit, and that's perfectly okay. If you're comfortable with that, great. If you're not comfortable with that, though, then it's time to make a change in your life and join a group or do something other than sit by yourself. And there's lots of ways to do that. There's plenty of opportunities. You just have to make a decision and put forth the effort to have the life that you want. But all of it is you. You control 100%. Okay.
ESTELLE: But no, I control it. that's not the point. The point is, I don't even what I want anymore. Like, I, the quiet. I like the routine.
LUKE: It sounds like you've been hitting the bottle, lady. What's your consumption level these days?
ESTELLE: Ha, no, I wish. Honestly, no, I'm stone cold sober standing in my kitchen at two in the morning staring at a lasagna. Which is maybe worse? Like, at least if I'd been drinking, there'd be a reason I couldn't answer a basic question about my own life.
LUKE: Well, you're not making complete sentences. You're not making any sense and you're meandering about nothing. You call the radio show you have no point to make. I'm not really sure why you're here. So, do you have something you'd like to talk about or should we move on to the next caller?
ESTELLE: Okay. Okay, here it is. I think I'm scared that I'm building a life that looks exactly like what I want on paper. The job, the solitude, the telescope, the desert. And it's going to be going to be eating that lasagna alone on a Wednesday.
LUKE: That's enough of you. Eat your lasagna. Stop whining. If you want to change your life, change your life. But you're a boring caller, and we don't need boring callers. Next time you call in, have something interesting to say. That's what I have to say. And next up, we have Leon. Leon, do not be a boring caller, please. Do your best to be an interesting and have something to say. Today is not Fat Tuesday, but we're pretending it is because I fell asleep on Fat Tuesday. And I've got some trivia for you. Are you ready for my New Orleans trivia?
LEON: Ha! Luke! Man, it is good to be back. Okay, but hold on. Before you hit me with trivia, I got to tell you something first.
LUKE: All right, hit me with some funny shit.
LEON: So I quit the boot camp.
LUKE: All right. The boot camp was for coding? Is that what that was about?
LEON: Yeah, the coding, Python, all of it. I'm not going to lie, I lasted about six weeks, and I spent probably five of them watching poker strategy videos instead of writing code. You and I talked about that, actually. Last time, you said something that kind of stuck with me. You basically told me that at 56, poker is just as legitimate as anything else I could be chasing.
LUKE: Sure thing. So are you chasing the poker now?
LEON: I entered a tournament. Last Saturday, down in Albuquerque.
LUKE: Wow, that's fantastic. Entering a tournament, though, anybody can enter a tournament at any time. What is your strategy for moving forward with your poker career? Tell us about your bankroll management.
LEON: Okay, so, I'm not going to lie. I did not expect you to know the word bankroll, Luke.
LUKE: Oh, I deserve that. That's impressive. Okay, so here's the thing.
LEON: I don't have a poker career yet. I want to be real clear about that, but I did cash.
LUKE: What was your tournament? What were the stakes and what did you cash?
LEON: It was a $65 buy-in at one of the card rooms down there. Nothing crazy. And I cashed for $180. So, I mean, it's not like I'm retiring from towing. But I'm not going to lie, I sat at that table for like four hours, and I felt more alive than I had in a long time. It's like I knew what I was doing.
LUKE: Yeah, well, I think you're going to find that a poker room, wherever you are in New Mexico, is a very different experience than if you were going to play with professionals in Vegas or anywhere else there were professionals. 180? That's a nice little win. But anybody can win once in a while. We call that beginner's luck. You're going to need a poker strategy. How much money do you have for your bankroll? Like, how much can you afford to lose before you're working off your own money?
LEON: No, you're absolutely right. And I know that. I'm not delusional about it. But here's the thing. The guys I beat on Saturday, some of them weren't chumps either.
LUKE: Okay, well, and that's great. Congratulations. But I'm trying to push you in the right direction here, sir, and you're not going with me. What stakes are you playing with at? Like, what's your game?
LEON: Okay, okay, fair. I hear you. Right now, I'm playing micro-stakes online, like $2, $5 stuff, and I've been studying, I'm not going to lie, way harder than I ever studied Python.
LUKE: Okay, and what are you studying? Are you studying the old Doyle Bronson books? Are you studying GTO? What is your strategy for learning and improving and being a professional poker player? If you're not going to... When I gave you that advice, when I told you it's not too late to start anything, I meant to start something. So just going to play poker at a card room on a Thursday is not starting something.
LEON: Yeah, yeah, okay, that's fair. That stings a little, but that's fair.
LUKE: Well, congratulations on your journey here. I hope it goes well for you, and you already got a win under your belt, and that's nice. But it's going to be a long road, a lot of grinding. And if you want to be successful at poker, you're going to have to have a plan. And it sounds like you don't. So I recommend buckling down and seeing how the pros do it and follow a strategy.
LEON: I hear you, Luke. I do. And I'm not going to lie. I've been thinking about this a lot since last time we talked. I've been watching some of those poker training videos, the ones where they break down hands and talk about ranges and all that. It's a lot.
LUKE: Yeah, it's a lot, and it sounds like you don't quite understand it. So before you get into a position where your life is on the line on a poker game, I recommend getting these basic fundamental, at least a poker words, you know, the language, get this under your belt.
LEON: No. You're right. You're right. And honestly, I'm not going to lie. I called tonight thinking you were going to be a little more excited for me about the $180, and I was going to feel good about myself and go to bed. And instead, you're kind of doing what my daughter does, which is just cut right through it.
LUKE: Well, you can still feel good about yourself. You want money in a poker game, and that's pretty cool. But we're talking about your life and career going forward, and $180 in a poker game isn't going to pay your mortgage.
LEON: No! No, it's not. And Amber would, she would have something to say about that if I suggested otherwise. So here's the thing. Here's why I actually called tonight. I dropped the boot camp.
LUKE: Yeah, you told me that. You dropped the boot camp because you're going to follow your poker dream.
LEON: No, no. I dropped it because I got into UNM. Full ride. They saw my work history. They saw my age. They saw that I was serious.
LUKE: Oh, I'm sorry. I thought I was under the impression that you dropped the boot camp to become a poker professional. But if you're just doing this as a hobby for fun and you want $180, congratulations, you can feel good about yourself and go to bed. But also, congratulations for your entry into UN&M. What is it for? A full ride for what?
LEON: Computer science. Full ride. And I'm not going to lie, Luke. I'm scared shitless. I got the letter today, and I just sat in my truck in the parking lot at work and work and read it three times. And then I called Amber, and she just started crying. And I don't know if it was happy crying or terrified crying, but I think it was both.
LUKE: Either way, it will be an adventure, and you've got yourself a full ride to being unemployed. Congratulations. Are you ready for the trivia?
LEON: Wait, wait, wait, wait. Hold on.
LUKE: All right. That was Leon. Leon's a tough one. He calls in sometimes and he just wants me to tell him everything's great. And everything's not great. Oh, good for you. You went to a poker room. You played a tournament and you got lucky. That doesn't mean you're good at poker. That means you bet 40 bucks and won. Okay, next. Heather, Heather, welcome to the show. What's going on, Heather? Thanks for calling in. Happy fake Fat Tuesday. Today is the Fat Tuesday for people that fell asleep during Fat Tuesday. I've got some trivia for you. Are you ready for your question?
HEATHER: Oh, I'll do the trivia, sure, but okay, I have to say something first because I've been sitting here for like 40 minutes waiting to get on, and I will absolutely lose my mind if I don't say it. I just told my mom no. Like, to her face. Well, on the phone. But I said no to my mom tonight, and I feel like I committed an actual crime.
LUKE: What did you say no about?
HEATHER: She called me three hours ago asking if I could loan her $400 for a furnace repair. And I said no. I said the actual word no to my mother, who is cold, Luke. She's cold right now, potentially. And I'm sitting here in the dark at work feeling like the worst person in New Mexico.
LUKE: Well, why did you say no, over 400 bucks, if your mother is freezing in New Mexico?
HEATHER: Because I am 17 thousand dollars in debt and I literally do not have four hundred dollars. Like, that's not a figure of speech. I have maybe 80 bucks until Friday.
LUKE: Okay, well then you can't give her the money. No big deal. You've got to say no. If you don't have the money to give her, you can't say yes. That would be strange. And then, you know, when she comes to get the money, you don't have it.
HEATHER: Oh, that'd be even worse.
LUKE: Yeah, I bet you would feel like a silly goose. So it sounds to me like you did the right thing.
HEATHER: No, I know, I know you're right. Logically, I know that. But like, Okay, here's the thing. My mom is not great at taking care of herself. Like, she's getting older and she's getting smaller, if that makes sense. And I'm watching that happen, and I can't, I just feel like saying no to her is the same as saying, I can't take care of you. And that feels really bad at 25.
LUKE: Well, you're 25. You don't have the money to support your parents financially. That's not strange. You can do what you can to take care of her. Do you have a warm place that she can go? Where do you live?
HEATHER: I mean, I'm at work right now. I do overnights at a group home, so yeah, I'm warm. She's over in, she's not that far. She's maybe 40 minutes away. And I'm sure she's fine. She probably has space heaters or whatever. I'm not saying she's actually freezing to death. I'm saying I feel like she might be, and I can't do anything about it even if she was.
LUKE: Well, that's a shitty place to be, but if that's where you're at, that's where you're at. You're going to have to continue working on your own life first. Make sure you can get it on your feet before you can support anybody else, even your parents. If there's something you can do to help, then great. If you can help her maybe get funding from different sources or look for, I don't know, churches that can help or, you know, get her tied into the community and any sort of health care benefits that she's eligible for, you can help her in that way. But if you don't have the money yourself, then you don't have the money for her and that's, you don't have to feel bad about that. Just do your best.
HEATHER: Yeah. Yeah, no, that's... Okay, the church thing actually isn't a bad idea, and I don't know why I didn't think of that. There's probably resources. I think I've just been so in my own head about it that I went straight to I failed her. Instead of like, okay, what else can we do?
LUKE: Yeah, I think a lot of people do that. And the thing is, it's not about you. That's a very common thing. Everybody makes everything about themselves. And this isn't about you. If you can help, then help. And there's lots of different resources out there that are available. You just got to track them down. If you're good at searching on the computer and stuff, then you have a better chance to find them than she does. But ask around, you work at a group home, so I'm sure that you're familiar with a big list of resources for housing and heating and phone assistance and utilities, all that jazz. So you can get her as settled as you can and do the best you can and don't feel bad when you can't take care of her, because the truth is it was her responsibility to take care of herself.
HEATHER: That last part hit different. Yeah. I mean, yeah. Yeah. I mean, yeah. No, honestly, it's true. And I think that's the part I've been avoiding saying out loud because it feels mean. But like, she's a grown woman and I'm her kid. And somewhere along the way that got flipped around, and I've just been accepting that as normal.
LUKE: Well, it's not as black and white as that. So I don't know your situation. I don't know your mother. I don't know if she has mental health issues or physical ailments or what the history is there. All I know is you're 25 years old. You're getting on your feet and you can't hold yourself responsible for the well-being of a grown adult that had 60 years to get themselves on the correct financial footing. So now that we're through that, you can feel all right. It's time for trivia. Are you ready for the trivia?
HEATHER: Wait, trivia? Okay. Yeah. I'm, yeah, hit me. I've been sitting in the dark for three hours. I might as well win something.
LUKE: Okay, well, you're not going to win anything, but we just want to hear your answer. So, a Sicilian immigrant invented a famous New Orleans sandwich in 1906 at the Central Grocery. What is it called?
HEATHER: Oh, that's easy. The muffilletta. My grandpa used to get them from this little place over in, well, it's not there anymore, but back in the day. He'd bring one home and it'd feed us for three days.
LUKE: All right. All right. Well, thank you for the call. Do your best with your mom. Try and help her out if you can. Don't take too much responsibility. And make sure that you're putting your own oxygen mask on first. That was the correct answer. I've never had a muffletta. I don't know where a muffletta is, but it sounds gross. Anyway, it's about time for a word from our sponsors. All right, folks. It's time to take a break to thank today's sponsor. The leader in Maple-based Marmalade. If your toast has been living in a bland little studio apartment of flavor, it's time to move into the luxury suite.
LUKE: We're talking maple-based marmalade. Plus, marmalade-related products, because apparently Jam Hospitality asked, what if breakfast had a brand ecosystem? Go to www.myfavoritmarmalade.com and use code jam it in you for 15% off. That's Jam it in you, like jam it in you, but legally distinct and emotionally complex. Jam hospitality. Put it on toast. Put it on pancakes. Put it on your confidence. That's www. my favorite marmalade.com. Tell him Luke sent you. You're welcome. All right, and we're back. Archie. Archie, welcome to the show. How are you? Happy Fake Fat Tuesday.
ARCHIE: Oh, man, Luke. I'm about three cups of coffee and a half pack of camels past happy. But hey, Fake Fat Tuesday. Does that mean I could eat a king cake and still call it a salad? Yep. Because I could use the sugar rush right now. So here's the thing. I just found out my landscaping companies getting shut down in nine days. And they made me sign a non-disclosure agreement they told me. Like, here's your pink stuff. slip, here's a pen. Don't tell the guys you've been working with for three years that we're ghosting them. And tomorrow morning, I'm supposed to bid a residential job with Tommy and Jesse, guys who've got families, who think they've got work through March.
LUKE: Okay, so just to get it right, you signed a non-disclosure agreement, an NDA, as we like to call it in the business, to not mention anything about the landscaping company being closed because I guess you're getting paid off or something like that. They would have you sign an NDA. And the first thing you do is call a radio show and tell the world about the landscaping company. How does that work?
ARCHIE: Yeah. Okay. I hear you. I hear you. In my defense, I didn't use the company name. And also, it's like two in the morning and I'm sitting in a Jewel Oskow parking lot in Chicago. So I'm not exactly thinking like a lawyer right now. That's actually part of a. why I'm calling. I'm trying to figure out if I even need to worry about that NDA. Because I genuinely don't know if what they made me sign is even legal. Like, can a company actually make you sign something that says you can't warn your co-workers that they're about to get blindsided?
LUKE: Yes, they can. And you did sign it. So if I were you, I would keep my fucking mouth shut. But if you can't do that, it's unlikely that they would be able to be able to enforce anything on you. They're not going to sue you for telling Pablo that he doesn't have to go to work tomorrow.
ARCHIE: Right, right. And that's, okay, that actually makes me feel a little better. Because what are they going to do? Sue a guy who's about to be unemployed? Like, come after my 2009 F-150 in my collection of gas station sunglasses. Good luck. But here's the part that's actually keeping me up at night, Luke. It's not even the NDA.
LUKE: Okay, what is it?
ARCHIE: It's Tommy and Jesse. Because tomorrow morning at 8 o'clock, I'm supposed to sit across from these two guys at a diner and walk them through a bit on a residential project. Let them measure square footage and talk about mulch prices and act like they've got six weeks of work lined up. And I know the second they cut me loose, the company's going to ghost that contract. Like it just disappears. And these guys are going to be standing in some homeowner's driveway. wondering what happened.
LUKE: Well, for your meeting tomorrow, are Tommy and Jesse being compensated for their time?
ARCHIE: That's, ha. I mean, no. No, it's a bid. Nobody gets paid to bid a job. You show up. You measure. You talk numbers. You hope you get the work. So they're not losing money tomorrow exactly. They're just, they're losing time they could spend, lining something else up.
LUKE: you're sick tomorrow and that's how you handle it. You know?
ARCHIE: Oh, I thought about that. I really did. But here's the thing. Tommy's got a kid with asthma. And Jesse's wife just got laid off from the school district. They're counting on this. And if I don't show up, they're going to think I'm flaking on them. And then they'll be scrambling to find another crew to bid with. And that's just, that's another week of uncertainty for them. And I can't do that.
LUKE: Well, you can't do that. that. And it sounds to me like you don't really have to do anything here. It's not you closing down the company. And you promised the company in writing that you wouldn't tell anybody. So if you have any sort of pension or severance package on the line here, I think that's what you would risk by breaking the NDA.
ARCHIE: Yeah, no, you're right. And I know you're right. And I know you're right. There's no pension. I'm a landscaper, Luke. Not a longshoreman. But there is severance. And honestly, I think that's the only reason I signed the thing in the first place. Like they put the paper in front of me and I just, I signed it. Monday afternoon still had dirt under my fingernails. And I just signed it.
LUKE: Well, then you're in the position you're in. And you can't worry about everybody else. So I would, if I were you, I would start looking for a new employment opportunity.
ARCHIE: Yeah. Yeah, that's. I know. No. And I've been doing that. Or I've been telling myself I've been doing that. But mostly what I've been doing is driving around Juulosco parking lots at midnight. So, you know, productivity's been high.
LUKE: All right. Well, you know what you've got to do now. And now it's time for fake Fat Tuesday trivia. Are you ready?
ARCHIE: Oh, yeah. Sure. Hit me. I'm already in a parking lot at midnight. I got nowhere to be.
LUKE: New Orleans hosts the oldest continuously running gay Mardi Gras crew in the country. What's it called?
ARCHIE: Oh, man. I have absolutely no idea. The Beads and Boas?
LUKE: No, sir. It's actually called the Crew of Yuga, founded in 1958. How progressive. Thank you for the call, sir. I'm sorry to hear about your situation at work. If I were you. I would keep my mouth shut and honor the NDA and try to help, try and help out your friends as much as you can in some other way after they get the news. All right, we're moving on. Thank you again for the call. Misty, Misty, welcome to the show. How are you tonight?
MISTY: Not great, Luke. I came home from Tucson tonight, and my brother threw a party in my house while I was gone. destroyed the place.
LUKE: And destroyed how? How bad?
MISTY: Soaked carpet, cracked flat screen, boot-shaped hole in my bathroom drywall, and someone ripped the handle clean off my refrigerator. Like, the handle, off the refrigerator.
LUKE: Oh, that's not good. What is the carpet soaked in?
MISTY: You know what, Luke? I did not investigate that closely, and I am choosing not to think about it right now.
LUKE: Okay, and what's your brother have to say for himself?
MISTY: He woke up confused, like genuinely confused why I was upset, said it was just a few people, just a few people from animus, tore through my house, and he's looking at me like I'm the unreasonable one.
LUKE: Well, those animus people are pretty rowdy. I'll give him that. And it sounds like your house is trash pretty good, and he's going to have to have a better answer than that. So is he going to help you pay for the damages and the repairs?
MISTY: Oh, he, like, real generous. He said he'd help me out with some of it. But Travis's idea of helping out is showing up with a case of beer and a shop vac he borrowed from his buddy's uncle. He doesn't have a job, Luke. He's been between gigs since last summer, which in Travis speak means he's been living off my leftover takeout and whatever he can scam off his friends. And now he's got the nerve to act like I'm being dramatic.
LUKE: Well, it sounds to me like it might be time. to cut some ties with your brother. You know, let him, let him figure out his own thing and stop their own parties at your house. Because, uh, you shouldn't have to support a grown-ass man.
MISTY: No, and that's the thing. I know you're right, and I've known your right for a while now. But he's my brother, you know, like our mom passed three years ago, and it's kind of just been us since then.
LUKE: Well, just to help you out, because this is the kind of guy I am, I'm going to give you a shot at redemption, all right? I got an easy one for you. You ready?
MISTY: Okay, yes. Yes. Give it to me. I'm ready. I'm ready. I'm focused.
LUKE: That's what she said. Okay, the song, House of the Rising Sun, is about a New Orleans establishment. What kind of place is it?
MISTY: A brothel.
LUKE: That is correct. Congratulations. Redemption. Hold on. We got to play the correct sound so you can get the full experience. All right. Nice job.
MISTY: Oh, thank God. Okay. Yes. I needed that. I was sitting here, sweating and my truck.
LUKE: All right, Misty. Thanks for the call. Sorry, your place got trashed. It's time to get your brother out of there and move on with your life. So that doesn't happen again. So I hope you can get it all cleaned up and investigate what the carpet is soaked with. Maybe replace that carpet. And now, folks, it's time for another word from our sponsors. This episode is brought to you by Nile Nosh. Meal kit for people who've given up but still need to eat something. Look, we're not going to promise that it'll change your life. You're still going to die alone. But at least you can spend your remaining Tuesday evenings chopping vegetables you can't pronounce. Each box contains three meals that take 45 minutes to make when they could have taken eight because suffering is the only authentic human experience. This week's menu, Horissa glazed despair with quinoa, panseered regret over arugula, and our customer favorite, chicken thighs with preserved lemon and unprocessed drama. Use code void at checkout for 15% off your first box. That's code void, because we're all just screaming into one. Nile Nosh. You got to eat something. Might as well be complicated. Okay. And we're back. Next up on the line, we've got Chenequah. Chenequah, thanks for calling. And happy fake fat Tuesday.
CHENEQUAH: Ha, happy fake fat Tuesday to you. You too, Luke. Okay, so I hit somebody's car last month and drove away. And now I see that car everywhere. And I think the universe is absolutely torturing me for it.
LUKE: As it should be. What are you going to do about that? You're just going to live with it?
CHENEQUAH: That's the thing. I don't know. Like I keep telling myself I'm going to do something. And then I just don't. And I'm a social worker, Luke. 33 years. I sit across people every single day and tell them the hard thing is always the right thing.
LUKE: Well, do you have insurance?
CHENEQUAH: I do. Yeah. Full coverage. Which actually makes this worse, right?
LUKE: Yeah, it does. So I don't know what you're calling in looking for me to tell you. Obviously, what you should do is talk to the owner of the vehicle that you hit and make it right. That's the right thing to do. That's what you should do. And not doing that is a crime. And it makes you a bad person.
CHENEQUAH: Oh, I know I'm a bad person, Luke. That's not the part I'm struggling with. I'm a bad person. I just... I want to do the right thing. Sorry, I fumbled that. I want to do it. I know exactly what I need to do it. I know exactly what I need to do. I just, every time I see that car, I kind of freeze up and then I drive past it, and then I hate myself for the next two hours.
LUKE: Do you know the owner of the car?
CHENEQUAH: No, that's the thing. I don't know who it is. I mean, I know it's a white Nissan with a crumpled bumper and a cracked tail light, but I don't know who owns it.
LUKE: Well, here's what I think you should do. I think you should take yourself down to the police station, explain what happens, say you panicked, and come clean about the situation in case maybe they're looking for someone. So you can save a lot of people a lot of time and then help the owner of that vehicle get it fixed through your insurance.
CHENEQUAH: Yeah. Yeah. I know you're right. And the thing is, and I know how this sounds, but I keep driving past that police station too. Like, I have driven past it.
LUKE: Okay. Well, what would you like me to tell you?
CHENEQUAH: I don't know, Luke. I don't know what I'm calling for. I just, I keep seeing that car. It's like it's me store and there it is. I'll be at the clinic where I work. And there it is.
LUKE: Well, it sounds like you've got a lot of opportunities to make things right. So it's about time you do that. Hey, we're doing trivia and I've got a trivia question for you. Are you ready? Since you clearly don't have anything else to talk about?
CHENEQUAH: Oh, God. Okay, yeah. Hit me. I deserve that.
LUKE: Okay. What New Orleans cocktail was invented at the Roosevelt Hotel Bar and is made with cream, orange flower water, and soda.
CHENEQUAH: Oh, that's easy. It's a Sazirac, right? Wait, no, that's not right. The Sazirac doesn't have cream or soda. Hmm. Wait, is it a Viochere? No, that's got cognac and vermouth.
LUKE: No, it is the Ramos gin fizz, and it requires 12 minutes of shaking. Bartenders used to form relay teams just to mix one of them. All right. Thank you for the call, Shaniqua. You're going to have to do the right thing or not do the right thing, one way or the other. But either way, you're a horrible person, and we appreciate your call. Okay, thank you, Shaniqua. Next up, we got Curtis. Curtis, welcome to the show. Happy fake fat Tuesday.
CURTIS: Hey, Luke. Thanks, man. So, my sister has been on dialysis for 18 months, and I just read that their 3D printing kidneys now, and nobody at her clinic has said a single word about it.
LUKE: Okay, maybe they don't have the ability to 3D print a kidney in your particular clinic.
CURTIS: This is nowhere in New Mexico. We don't really have the best medical equipment here. No, I hear you. But this wasn't in New Mexico. This was Baltimore. That's even worse. A woman in Baltimore got one. And I'm sitting here at the Flying J at two in the morning thinking, okay, so this exists. This is how to do you happening? Why does nobody at the dialysis clinic even mention it? Like is Denise supposed to just keep driving four hours round trip to Albuquerque three times a week forever? Or is there actually something out there?
LUKE: I think driving four hours round trip to Albuquerque three times a week forever is the business model, sir. So yes, that's exactly what they wanted to do. There may be something out there.
LUKE: Have you done any independent research into 3D printable kidneys? Maybe that's something that you can push for. or, you know, advocate for.
EARL: I wouldn't expect the medical industry to really ever push you in the right direction. That's not how they get paid.
LUKE: No, that's, yeah, that's a dark way to look at it, but I don't think you're wrong.
EARL: And honestly, that's kind of what's eating at me. Like I read this one article on my phone between customers tonight, and now I don't know if I'm holding on to something real or if I just found the one hopeful thing on the internet at three in the morning and I'm running with it. Because I won't. want to. You know what I mean? Like, is this actually close or is this a checkback in 2035 inches situation?
LUKE: I think it is close from similar articles that I've read about hearts and replacement shoulders. And I think it's very close. And the truth is 2035 is very close. So I don't know that it's going to be this year or next year. Maybe they're already doing it. I don't know. You might have to go to L.A. or something. Or, um, Boston or somewhere where, you know, they actually study medicine, not Baltimore.
EARL: Right, right. And that's the thing. Denise is in Arizona. She moved out there a few years back, and I'm over here in New Mexico, and she's got this clinic situation locked in, and I don't even know if she's on a transplant list. I don't know if she's looked into any of this. And now I'm the guy working the graveyard shift at a truck stop who read one article and is calling a radio show at three in the morning like I cracked the code. But somebody's got to be. paying attention, right?
LUKE: No. It's everybody's responsibility to pay attention when things relate to themselves. Like, it's her responsibility. But you're in a position where you're working in the night shift and you have time and you seem somewhat interested in this subject. So maybe you can use your time at work to do that research and maybe find her a better deal and be her advocate. And that would be a good use of your skills.
EARL: That's, yeah. Yeah, you're right. And honestly, that hits a little harder than I expected at three in the morning. Because I think part of why I'm so wound up about this is that I feel useless. Like she's the one doing the driving and the needles and the whole thing three times a week, and I'm just, I'm here.
LUKE: Well, you're not the one that needs the dialysis, so it makes sense that she would be doing it. And that's unfortunate. I'm not saying she deserves it or you don't deserve it. We get what we get. But in your situation, you're not the one that's sick, so it's not on you. But if you can help and you have the time to help and you have the ability to help, then that would be a good thing for you to do. And maybe you can help others as you learn more information about this.
EARL: No, I hear you. And I know it's not on me. But that doesn't stop me from feeling like I should be doing more. Like, I'm the one who's got the time to sit here and read articles between customers. And she's the one who's got to live with it. And I don't know. Maybe that's just how it is. But it still feels like I'm letting. her down by not knowing more.
LUKE: Well, you are not. It's not your responsibility to know anything about dialysis. So the fact that you even read the one article and care enough to call a radio show and talk about it says a lot. I think if you want to invest your time into helping her out, maybe you can make something happen there or at least let her know that you tried, you know, and sometimes that's enough. It's a horrible thing that people have to go through. And hopefully future soon. They can 3D print new parts for people when they fail. That's, I think, what we're all hoping for.
EARL: Yeah. Yeah, man. I hope so too.
LUKE: Okay, so we're doing fake Fat Tuesday trivia. I've got a question for you. Are you ready?
EARL: Oh, I'm ready. Hit me. I got nothing but time out here.
LUKE: All right. New Orleans is known for second lines. What exactly is a second line?
EARL: Okay, so a second line is basically this wild, unplanned parade that happens after a funeral in New Orleans. The main funeral procession is all somber and formal, right? But then, after the burial, the family and friends, and just who wants to join, musicians, dancers, random folks off the street, all start following the band again. But this time it's a brass bands play, people dance in the street. It's all about celebrating the person's life instead of just mourning. Yeah, man, it's a pretty cool. cool thing.
LUKE: I don't know why other places don't do that. It's just a big party when somebody dies. So everybody mourns real quick and then everybody parties behind them. That's the way it should be. And it's a cool thing that New Orleans does. And we appreciate the city of New Orleans for that.
EARL: Hell yeah, that's the spirit. I mean, if you're going to go, might as well go out with a brass band and a two-step, right? None of this quiet, sad funeral nonsense. Just send them off with a good time.
LUKE: you and thanks for the call. Don't feel too bad about your involvement and your sister's medical procedures. She's going to have to go through what she has to go through. And if you can help, great, do what you can. But don't take it upon yourself to be responsible because it's not on you. You're not her caregiver. And anything you do is you trying to help. We got one more caller today. And our caller is Carmen. Carmen, thanks for calling in. It's a Big Fat Tuesday, you're the last caller of the night. I've got some trivia for you. Are you ready for it?
CARMEN: Wait, hold on. Before you hit me with trivia, can I just? I just pulled my clarinet out of a closet for the first time in 10 years, and I don't know what to do about it.
LUKE: Well, how about you play us a little ditty?
CARMEN: I wish. I opened the case and just kind of stared at it. I don't even know if it's still in playable, can't.
LUKE: How long you play the clarinet for?
CARMEN: From when I was about nine until I was 18. So almost 10 years. I was good, Luke.
LUKE: You play the clarinet for 10 years and you don't know by looking at one if it's in playable condition?
CARMEN: Okay, fair. The reeds are probably shot. The pads might be dried out. I know what to look for. I just haven't let myself actually look yet. I've been sitting here with the case open for like an hour, just not touching it.
LUKE: Okay, why aren't you touching it? If you pulled it out, you obviously want to play, you're thinking about it again. You've played for 10 years, so you certainly know how. I think it's fairly easy to get reeds and replacement parts at a music shop these days. What's the problem?
CARMEN: Because I saw something tonight that kind of cracked me open a little bit. There's this woman, She got a scholarship to University of Arizona 28 years ago, a scholarship that I turned down. And she just posted on Instagram that she's going on a national tour with the Santa Fe Chamber Orchestra, playing the same piece I used to practice until my fingers went numb.
LUKE: Well, obviously, you didn't want to follow that path or you wouldn't have put the clarinet down for so long. But since you did, if it's something that you want to get back into, You still got time. You can still play that piece and you can still join an orchestra. You don't necessarily need a scholarship to a university. There are plenty of chamber orchestras that will take you.
CARMEN: Oh, Iyat. I wanted it so bad I could taste it. But my mom left when I was 16, and my dad was working two jobs, and my little brothers were still in elementary school. Someone had to keep things together. So I stayed home, took the community. college route and ended up working southwest routes out of Phoenix. And now my dad keeps showing up at my place unannounced since he retired, reorganizing my kitchen cabinets and asking why I never play anymore.
LUKE: Okay, well circumstances came up. Your life took an unexpected turn and you dealt with it. And now you're at a different place. You obviously are self-supporting.
LUKE: Your father is retired. And you can get back into the clarinet. If you want to, if it still calls to you, then it's not something that you're going to forget. It's there for the rest of your life. And if an orchestra is something that you want to be a part of, you can absolutely do that.
CALLER: I know. I know you're right. And that's almost the scarier part, right? Like, it's not impossible. There's a community orchestra over in Las Cruces. I've driven past their sign a hundred times. Terrible. If 28 years did what 28 years does, then I have to actually grieve something.
LUKE: Well, hey, I mean, realistically, you're going to pick that up. You are going to be terrible in comparison to how you were 20 years ago. But you'll pick it up quickly. You just got to put in the time. And if you love the clarinet, then you have no problem putting in the time. And you'll get back up to speed. You get your chops back. And I'm sure that the last cruis orchestra is not, um, not, I don't want to say not great, but I mean, your ability level will probably fit in well with a smaller orchestra like that.
CALLER: Huh. No. That's okay. That's actually really funny and also probably true. And kind of a relief. Like, I'm not trying to get to Carnegie Hall. I just want to play again. I just want to play again. I just want to feel that thing again where you're so inside the music that everything else just
LUKE: Well, you can do that. You've got that clarinet right there. All you got to do is run down to the music store, get some reeds. If you don't have a music store, then order them online. But they're not expensive. You know what you need to do. Pick up the clarinet and play some music and do that every day for a while. Get your chops back up and then think about joining that orchestra because there's nothing cooler than listening to an orchestra.
CALLER: Yeah, yeah, you're right. And honestly, the clarinet's right here in front of me. I've been staring at it for an hour, like it's going to bite me. My dad's going to lose his mind when he finds out, by the way. He's going to show up and reorganize my living room and take full credit for it somehow.
LUKE: Hey, good for him. You know, he's got to get some small wins.
CALLER: That's okay. That's fair. He does. He really does. The man raised three kids by himself. And now he's retired and lonely and showing up at my place moving my spice rack around. Let him have it. But, okay, I have a question for you.
LUKE: All right. Hit me with it. What's your question?
CALLER: Do I play it tonight? Like right now, after we hang up? Because it's almost, what, one in the morning? I've got a flight out of Phoenix tomorrow. But I'm sitting here. And the case is open and I just, I kind of want to.
LUKE: Well, you said that it had dried out reeds and it may not be in playable condition, but if you can play it, yeah, sure, you should. But it's not one in the morning. It's closer to quarter of four in the morning. And you're calling Luke at the Roost. It's our fake fat Tuesday special. And I have a question for you now. It's my turn. Are you ready for my question?
CALLER: Quarter of four? Oh my God. I have to be up in. Okay. Yes. Yeah. Hit me with your question. I'm ready.
LUKE: Okay. What is a neutral ground in New Orleans? And why is it called that?
CALLER: Oh. Oh, no. Okay. Um, is it the median?
LUKE: It is the median. You're correct. Canal Street once served as the boundary between Creole and American settlements. In the middle was considered a neutral territory. So that's why they call it neutral territory. Thank you for the call. Congratulations on your newfound reinvigoration into music. I hope that goes well for you and that you get into that orchestra. We'll talk to you a little bit later. That's the end of our show.
@@ -0,0 +1,309 @@
LUKE: Okay, welcome back to the show. It's Luke at the Roost. Tonight is Wednesday, February 18th. It's 11.11 p.m. And we're in a windstorm here in the desert inanimate, but that's not going to stop us. Little wind never stopped a good radio show. That's what I always say. If you'd like to call into the show, our numbers 208-439-58-3. That's 208-439 Luke. and you'd like to participate, you can send us an email. Our email address is submissions at lukeatsderoose.com. So, that all being said, I hope everybody's having a good night out there tonight. We're going to get right to our show. First up, on the show, we've got Chester. Chester's calling in. What's going on in your life, Chester?
CHESTER: Luke, hey, so I just finished about an hour ago, and I'm sitting here at work absolutely wrecked. And I got to ask you something about it, about this. I work nights at the prison down in Donia Ana County, and I read the last chunk of it during my rounds tonight. Father and son pushing a shopping cart through literal apocalypse, right? And the father would die for this kid. Does die for this kid, basically. And here's the thing. My girlfriend Sarah is up in Albuquerque, and I drive three hours each way every other Friday to see her. Been doing it for eight months. Okay, so where's the thing? canceled on me. Always some excuse. Works crazy. My roommate needs me, whatever. And I'm sitting there in B Block at two in the morning with tears in my eyes because Cormac McCarthy just destroyed me with this father who'd walk through fire, and I'm thinking, I'm the one doing all the walking here. She won't even meet me halfway in Socorro.
LUKE: Well, maybe she's not the right one, or she's not that interested in you, or she's busy. Her life could be busy. You don't know. What's her life like? Does she have other responsibilities she's got to take care of? Does she not have the money to pay for the gas, maybe? There's a lot of reasons she might not be able to meet you halfway. Are you talking about the road?
CHESTER: Yeah, the road. Sorry, should have said that up front. And look, she's a manager at a brew pub, makes decent money, drives a newer Tacoma than mine, no kids, no sick parents, nothing like that. I've asked her straight up, can we split the drive sometimes? And she just says Albuquerque's better because there's more to do. All her friends are there, which, fine. But then don't cancel on me four times because your roommate's having boy problems where you picked up an extra shift you didn't have to pick up. And here's what's eating at me. The father in the book, he's got nothing.
LUKE: Well, it sounds like maybe she's not the right one for you or you're not the right one for her. And if you're looking for more from her and you're not getting it, then move on, that's all.
CHESTER: Yeah, yeah, you're right. I know you're right. It's just I keep making excuses for her the same way I'm sitting here defending her to you right now. Oh, works busy Albuquerque's better. Like I'm trying to convince myself.
LUKE: What's your situation? Like, where do you live? What's your environment?
CHESTER: I'm in Los Cruces. Got a little one bedroom off Lohman, nothing fancy, but it's mine. Work overnight at the prison three, four nights a week, depending on the schedule. Been there two years.
LUKE: Okay, so you're in Las Cruces. There's plenty of people around there. be too hard to do.
CHESTER: I mean, yeah, there's people. But I don't know, man. I work nights, sleep days, my social life is basically non-existent. Most of the guys I work with are married or they're 20 years older than me. I'm 28.
LUKE: Yeah, you're 28 in Las Cruces, New Mexico. You can throw up a little Tinder ad and see who bites. But are you in love with this girl?
CHESTER: No, that's the thing. I'm not. me so hard tonight because the father in it he loves that kid so much he'd die for him without thinking twice.
LUKE: Don't they eat people in that book?
CHESTER: Yeah they do. There's these roving gangs. They keep people in basements like livestock. It's brutal, but that's what makes the father's love for the kid so, I don't know. Pure? He's protecting him from all that.
LUKE: Well maybe you should eat a little more of her and she'd be more inclined to come visit.
CHESTER: Jesus, Luke. Okay, fair point. But honestly, last time she was down here, things were...
LUKE: Things were what?
CHESTER: Off. Like, we had sex, but it felt like she was checking a box, you know? And then she spent half the weekend on her phone texting her friends in Albuquerque. I'm driving six hours round trip every other Friday to feel like an obligation.
LUKE: Yeah, don't do it anymore. I mean, you're not in love with the chick. She's really far away. of women around you. You can move on or not. You know, it's okay being single too. It sounds like you don't really keep a consistent, well, I mean, you don't keep a normal daytime schedule like most people. So when do you have time to spend time with her?
CHESTER: That's exactly it. I don't. My days off are like Tuesday and Wednesday. She works a normal job, nine to five. So even when I drive up there, she's got brunch plans with her girlfriends on Saturday, or she's tired from her week, and I'm sitting in her apartment waiting for her to have time for me.
LUKE: Yeah, that's disrespectful, so, I mean, you should just cut that out. There's no reason you should be doing that. She's obviously not into you.
CHESTER: You're right. I know you're right. It's just, I keep thinking if I just try harder, show up more, be more understanding about her schedule, then she'll, but that's the thing with the book, read the book.
LUKE: I saw the movie and all I remember from it was that they ate people.
CHESTER: Yeah, they do. There's these roving gangs. They keep people in basements like livestock. You already said that. It's brutal. But that's what makes the father's love for the kids so, I don't know, pure. He's protecting him from all that.
LUKE: Okay, so break up with your girlfriend because you don't have time and she doesn't have time and you're not into each other and there's no reason to be spending the gas money. That's what I say to you. Break up with the girl.
CHESTER: Yeah, yeah, you're right. I'll do it. I'm not driving up there this Friday.
LUKE: All right, good for you. You do it. That's, that's, uh, that's the can-do spirit that we look for here at Luke at the Roost. Next up on the radio show, we have Terry, Terry, welcome to the show. What's going on? Do you eat people?
TERRY: Oh, Jesus. No, what? No, I'm a nurse. I mean, I have some stuff, but sorry. I'm just sitting in my car in the hospital parking lot right now, and I'm kind of losing it. I found out three days ago that I'm adopted, and my mom, well, the woman who raised me, she's had a stroke, and I just found all these papers at her house, and I don't know what to do with this information.
LUKE: What are you thinking? You just found out that you're adopted, and your adopted mother, who you can call your mom, had a stroke. That's horrible. Are you around her to support and just be with her in her awful time of need?
TERRY: Yeah, I mean, I've been at the hospital every day. I just got off a 12-hour shift and I'm supposed to go back in to see her before I head home, but I'm sitting here in the parking lot just frozen. What were the papers?
Official adoption documents dated six months after I was born, April 1980. My name, Terry Lynn, a different mother's signature, the whole thing notarized and legal. They were in a box in the back of her closet, with old tax returns.
LUKE: Well, all that proves is that this woman loved you, and now she's sick, and you're there to care for her, and that's a good thing. So you don't have to worry about the being adopted thing. Lots of people are adopted. As long as she took care of you, she obviously made a decision to love you and raise you, and you're a nurse now, so you've made a decision to help people sounds ideal.
TERRY: I know. I know that. And you're right. She raised me. She loved me. She loved me. But, Luke, it's been 44 years, 44 years of her looking at me every single day and never saying a word.
TERRY: Why would she say a word? She doesn't, there's nothing to say. There's nothing to say? I have a brother, Ray. He doesn't even know yet. I don't know if he's adopted to, or if it's just me. I don't know who my actual birth mother is or why she gave me up. I don't know if my dad, if he was my real dad, or if he knew.
LUKE: Well, there's probably a good reason for that. Your birth mother might not have wanted you to know that she existed, and your adopted mother may have just been afraid that if you learned that you were adopted, you would abandon her in favor of your birth mother who abandoned you.
TERRY: Oh, God. That's, I hadn't thought about it like that. She was protecting herself from me leaving. But here's the thing. I always felt like something was off. My whole life I felt like I was performing, like I was trying to be the daughter she wanted instead of just...
LUKE: Instead of just what? I mean, what were you performing?
TERRY: I don't know. Being cheerful, being good, not being good, not causing problems. Ray was always the one who could just be himself, you know? He'd fight with her, slam doors, whatever. But I was always so careful, always trying to make sure she was happy with me.
LUKE: And why do you think that is? Does she give you a reason to behave in that way?
TERRY: No, not really. She was never mean or anything. But I always had this feeling. And maybe this sounds crazy, it. Like I had to prove I deserve to be there.
LUKE: Well, I think a lot of kids feel that way, and I'm going to ask you this, and it's not to be mean, it's not to be cold, but what makes you feel like you shouldn't have had to earn it? Like, why would you just expect it?
TERRY: I... Wow. That's a hell of a question, Luke. I guess because she's my mom, or I thought she was.
LUKE: Sure, but that's a... That doesn't mean you don't have to earn affection or likeability. She's your mom, and she loved you, and she raised you, and she provided for you, and she did all the necessary things. So what is it that you were earning from her or that you felt like you had to earn from her?
TERRY: The feeling that I actually belonged there, that I wasn't temporary, that she wouldn't change her mind. And now I find out that's exactly what I was. Somebody else's kid that she took in.
LUKE: Well, no, that's not what you were, because you were there for 44 years, right? So she raised you your whole life. She didn't ever treat you as temporary.
TERRY: No, you're right. She didn't, but she also never told me the truth. For 44 years, she let me think I was hers. And that feels like...
LUKE: Yeah, I'm sure it feels like a lot of things all at the same time, but if you try and separate yourself from the emotion of it and think about it logically and look at your life and how she treated you growing up. Was it all that bad? Does it matter, really? There are almost certainly good reasons that you were put up for adoption and that she adopted you. Maybe she couldn't have kids herself and she really wanted one. Maybe your birth mother was in a situation where she couldn't provide for you or was dying. Like, you don't know unless that's something something that you talk to her about. But I wouldn't feel like you've been wronged. It sounds, that sounds like an abundance of love to me.
TERRY: I hear what you're saying, Luke. I do, but she's had a stroke. She might not wake up. And if she doesn't, I'll never know why she didn't tell me. I'll never know who my birth mother was or why she gave me up. And Ray, my brother. He doesn't know any of this yet.
LUKE: So she had a stroke. She's in potentially the worst situation of her entire life and you're worried about whether you know why she gave you up or not. Like, why? It doesn't seem important.
TERRY: Because I spent my whole life feeling like something was off and now I know I was right. And yeah, maybe that makes me selfish when she's lying in a hospital bed, but I can't just turn off 44 years of wondering why I never quite fit.
LUKE: Well, why you felt like you never quite fit has nothing to do with her. That's entirely a you thing.
TERRY: That's not fair, Luke. How is it entirely a me thing when she kept this massive secret my entire life? When every time I looked in the mirror and didn't see her face looking back, every time someone said I didn't act like family, she could have just told me the truth and she chose not to.
LUKE: Because that's not a thing. People don't act like family. That's all about upbringing and your social environment. It doesn't have anything to do with your blood. People that are biologically related don't act a certain way because they're biologically related. They act a certain way because they grew up together. They share an intense, long-lasting life experience like you do with this woman who is in the hospital right now and your brother. And there's no reason you have to think any more about it there's nothing more to it than that. She loved you. You might not know what happened or why, but you can choose to go about the rest of your life with the understanding that you were loved and that there was likely a good reason in your best interest for the way those things played out.
TERRY: You're probably right. I know you're probably right, but sitting here in this parking lot looking at those adoption papers with someone else's signature where my mother should be, I keep thinking about all the times I asked her why I was so different. Why I was bad at the things Ray was good at.
LUKE: Because you're a different person than Ray. You know, it has nothing to do with who raised you or who your mother was.
TERRY: Okay, but then why hide it? If it doesn't matter, if blood doesn't matter, why not just tell me when I was 10 or 15 or 20? Why let me find out now like this, going through her files? Because she might die. That's what I can't get past.
LUKE: Because there could be a good reason for that. Your birth mother may have... That could have been a stipulation, like a non-disclosure agreement. There could be a very good reason that you don't know who that was.
TERRY: Oh, God. I didn't even think about that. Like maybe my birth mother didn't want to be found. Or there was something...
LUKE: Correct. And you can never know unless you can have that conversation with your mom. And you might not get that conversation. And if you think about it, logically, what would change in your life today if you had different information?
TERRY: Nothing would change. That's the thing that's killing me, Luke. I'd still be sitting in this parking lot in my scrubs. I'd still have worked a 12-hour shift. Ray would still be Ray.
LUKE: Exactly. Ray would still be Ray. Nothing would change. She would still be your mom. She would still be in the hospital. You would still be working, and everything would be exactly the same. So it really doesn't matter whether you know who your birth mother is or not. You could do one of those DNA test things or a genealogy situation. I don't know if that can go down to your actual birth parents. But why? Like, what is the point? It sounds like you had a decent upbringing. You're a grown adult with your own life now and maybe you feel like you don't fit in, but that's an unrelated problem to this.
TERRY: You're right. I mean, she raised me. She was there for every scraped knee, every bad boyfriend. Every time I called her crying about something stupid. That's what matters. Not some signature on a piece of paper I found in a filing cabinet.
LUKE: Exactly. And what matters is that she is in the hospital and she may not recover and you may not recover only have a little bit of time left with her. So I would wipe this from your mind and go spend time with your mom.
TERRY: Yeah. Yeah, you're right. I should go back in there. I've been sitting out here for almost an hour, just...
LUKE: Yep, you should go back in there. So do that.
TERRY: Okay, I will. Thank you, Luke. I really needed to hear that.
LUKE: You are very welcome, and I wish you all the best of luck. I hope she makes a full recovery. And then you can put this behind you and move on with your life. And with that, folks, it's time for a word from our sponsors.
LUKE: Look, I'm not a financial advisor. I'm a guy with a microphone and a dog. But the folks at Crypto Know asked me to tell you about their new decentralized investment platform. And I legally have to read this part. Past performance does not guarantee future results. This is not financial advice. And if you invest your rent money, you disqualify exactly what happens next. Crypto no lets you trade over 400 digital currencies, including three that were invented this morning, and one that's just a picture of my dog. The app features a real-time portfolio tracker with a built-in panic button that just plays ocean sounds when your balance drops. Crypto no! Fortune favors the bold, but it does not return their calls. It does not return their calls. It does not return their calls. And we won't return your calls, but if you call in, you can leave us a voicemail. And if I think it's funny, it might be read on the next show. So you should do that. Next up on the old radio show, we've got Travis. Travis, welcome to the show. What's keeping you up tonight?
TRAVIS: Hey, Luke. Thanks for taking my call. So I'm sitting here in my dark room, trying to work. And two hours ago, my daughter shows me this video that's been going around her school since Monday. me, my face, my voice, telling a room full of kids that the Navajo Nation doesn't deserve water rights. Except, I never said that. Someone took footage from different talks I've given and stitched it together to make me say the exact opposite of everything I actually believe.
LUKE: Well, that's pretty insane, and it's hard to believe anybody would say that. So, um, who do you think might have done this?
TRAVIS: I mean, that's the thing. I've been documenting pipeline construction and transmission lines for environmental groups and tribal councils for 15 years. I've testified against energy companies. I've worked with the Navajo Water Rights Commission. So take your pick, you know. Could be some kid who thinks it's funny. Could be someone with an actual axe to grind.
LUKE: That doesn't sound like a thing a kid would actually even know about or know to do. So it seems more likely to me that that's one of your enemies trying to slander you in front of children?
TRAVIS: It doesn't make a lot of sense. You're right, it doesn't. I mean, what ninth grade even knows who I am or cares about water rights litigation. But here's what's messing with my head. My daughter said kids are commenting on it, sharing it around. And I'm sitting here thinking, okay, if this is some coordinated thing, from an energy company or a PR firm, why target a school presentation?
LUKE: Yeah, there's a few strange things about where you're saying here. I don't think a video like that would go viral with high school kids. Maybe it would. I don't know. I've been out of high school for a while, so it just seems uninteresting to them. So do you have any known enemies? And do you have a relationship with the Navajo Nation?
TRAVIS: Yeah, I work with them pretty regularly, or I did. I've got contacts at the Water Rights Commission. I've photographed documentation for their legal cases. I've been out to Window Rock probably a dozen times in the last three years. As for enemies, I mean, there's the San Juan Coal Company. They weren't thrilled when my photos ended up in that lawsuit last year. There's a fracking outfit near Farmington that tried to get an injunction against me for trespassing on what they claimed was private land. But here's what I keep coming back to. If someone wanted to discredit me with the people I actually work with, with the tribal councils, or the environmental groups, they'd send it to.
LUKE: Yeah, absolutely. So you obviously have worked with the Navajo for a long time. You've got relationships in their council, I'm sure, and they know that you didn't say that, right? They're going to believe you when you say this wasn't me. I think at this point, everybody recognizes that this is possible and not that hard to do. So if there's a video of you saying something that is clearly against everything that you've ever stood for in your whole career, I think it should be pretty hard to dismiss. Strange to think about the why somebody would do this. What is the intention behind it?
TRAVIS: That's what's keeping me up, man, because you're right. The people who know me know me, they'll know it's fake. But my daughter's sitting there scrolling through comments from kids saying, wow, what a racist and this guy's trash. And she's like, embarrassed. She asked me if I actually said it. And I'm thinking, okay, maybe that's not about destroying my credibility with the Navajo Water Rights Commission.
LUKE: Well, that is pretty dark right there. If somebody's going to try and destroy your own relationship with your kid, uh, you must to really piss somebody off badly.
TRAVIS: Yeah, and that's what's getting to me. Because I can handle getting yelled at by some executive in a boardroom. I can handle threats. I've had those before. But watching my kid look at me like she wasn't sure if she could believe me. That hit different.
LUKE: Well, it could be another thing, too, that we haven't thought about. What if it's not your enemy, but it's an enemy of your kid that was trying to embarrass your kid by looking up information about you and publishing that to the school.
TRAVIS: Oh man, I didn't even, yeah, that's a whole different angle. She's a junior, so there's all that social hierarchy stuff I don't fully understand. She did mention it first showed up on some group chat she's in, but she seemed more confused than like targeted, you know? Like she wasn't saying, someone's coming after me. She was saying, Dad, what is this? But now, I'm wondering, does she have beef with someone I don't know about?
LUKE: She might have beef with somebody that she doesn't know about. I mean, this is the 2026 version of a bucket of pig's blood at the prom.
TRAVIS: Jesus, that's bleak. But you might be on to something, because she's been kind of quiet lately. I thought it was just junior year stress, college stuff. But what if someone's been messing with her, and this is just what finally bubbled up to where I could see it. I mean, how do I even ask her that without making it worse? Hey, honey, which of your classmates hates you enough to deep fake your dad?
LUKE: Well, you could just start with a simple conversation like, hey, I'm trying to get to the bottom of where this video came from and why it was distributed to your school. And there doesn't seem to be a good reason that any of my adversaries would target me in that way, because they wouldn't overwhelmingly, very lazy, and that takes a lot of coordination to pull off. There's a lot of information they had to pull up about who your kid was, what school they were, what chats she was in. Some oil company wouldn't know that.
TRAVIS: You're absolutely right. An oil company would just send a cease and desist, or try to get me fired from whatever freelance gig I'm on. They wouldn't know what group chat might Daufer's in. They wouldn't know her schedule well enough to time it so it hits, during the school day. That's someone who knows her world.
LUKE: Yeah, it must be, or a really, really twisted fuck that you really pissed off, and I think you would know who that was if there was that person. So it sounds more to me like it's her that is the target of this, and not you.
TRAVIS: Yeah, I think you're right. And now I'm sitting here in my dark room at midnight, feeling like an idiot, because I made this whole thing about me, about my work, my reputation, who I've pissed off over pipeline photos. Meanwhile, my kids getting targeted by some psycho 17-year-old with editing software, and I didn't even see it.
LUKE: Well, I have that conversation, see what it's about. There's probably nothing you can do about the video if it's already circulating, but luckily it's circulating in a small, contained space, and it's not going to be remembered. Nobody's going to hold on to that.
NORM: Oh, hey, Luke. Yeah. I'm good, man. Little wired, honestly. So listen, my best friend Miguel keeps asking me to move to Tucson with him, get a place together, and I keep making up excuses, but the real reason I won't do it is I think I might be in love with him and I have no idea what to do about that.
LUKE: Well, what makes you think he's not in love with you if he's asking you to move in with him?
NORM: Oh man, I don't see. That's the thing that's been driving me crazy. Like, he says it's practical, right? Split rent? We've always talked about doing that food truck thing. He's got connections with the school district down there. But then, like, last Christmas, we were both pretty drunk, and he looked at me in this way that I don't know, Luke. Maybe I'm reading into it because I want to see it.
LUKE: Is he gay?
NORM: I honestly don't know. He's never dated anyone seriously that I know of. Like, in high school, there were girls he'd hook up with at parties, but nothing ever stuck. And since then, nothing.
LUKE: Well, if he's your best friend and he's asking you to move in and you share that level of trust, it's going to be an awkward conversation, but I think you should ask.
NORM: Yeah, yeah, I know you're right. It's just, God, what if I'm completely wrong? What if he And then I lose him entirely.
LUKE: Well, then he wasn't a very good friend to begin with.
NORM: I mean, that's fair. We've been through a lot of shit together. He was there when my dad died. I helped him through his mom's cancer scare.
LUKE: Do you want to live with him?
NORM: Yeah, I really do. Like, I've been thinking about it constantly. What our kitchen would look like. Who'd take which bedroom? Stupid stuff like making coffee together in the morning. And that food truck idea?
LUKE: Well, if you're in love with the dude, I wouldn't move in with him without having that conversation first, because that could get real, real awkward. And then you've moved in, your stuff is there, just is much messier than to deal with that right out of the gate, you know? So talk to your buddy. Ask him like, hey, you into dudes?
NORM: Oh, God, just like that? Hey, Miguel, quick question before I pack up my life. You into dudes? You're right, though. I can't do this halfway. I've been sitting here in my bathroom with the shower running hot for like an hour trying to figure out how to text him back. And I keep typing, yes, let's do it, and then deleting it because I'm terrified of what happens after.
LUKE: Yeah, I think you got to talk to him first and have the awkward conversation. And one way or another, that'll tell you how to proceed. Because what happens if you have that conversation with him and you say, hey, I have feelings for you? I think it would be weird if we were living together and you didn't share those feelings. What if he's not gay? And he does say, no, I'm not gay, but I'd still like to live with you. Could you deal with that?
NORM: No, no, I really couldn't. That's the thing that's been eating at me. If I tell him and he doesn't feel the same way, I don't think I could just go back to normal. Like, watching him date women, being his roommate while I'm still feeling this way, that sounds like actual torture.
LUKE: Okay, well, then it sounds like you can just flat out, say, No, I don't think it's the right time for that and blow that request off. Or you can sit down and have the hard conversation.
NORM: The hard conversation. Jesus. Okay. Okay. So what do I even say? Do I lead with, I think I might be in love with you? Or do I ease into it?
LUKE: Well, I don't know. You're the one that has this problem. Don't put this shit on me. I have no idea. That's not how I I would approach it. What I would, I would just ask him straight out if he was gay.
NORM: You're right. You're right. I'm overthinking it. Just, are you gay or maybe, are you into guys? God, even saying it out loud makes my stomach flip. The worst part is there have been these moments.
LUKE: Well, it sounds like you already suspect that he might be gay and kind of into you. So there, there's probably something there. And also, So a dude asking another dude to move in with him is pretty gay. So you're not completely out of line in that line of questioning.
NORM: Okay, that's fair. Yeah. Straight guys don't usually text their buddy at midnight about splitting rent and building a life together. And last Christmas, man, we were both drunk and he said something about how I was the only person who really got him. And the way he looked at me...
LUKE: Yep. So just bring it up, be direct, ask him the question. You don't have to make it weird. It It doesn't have to be, well, it's a little bit weird no matter what, but it doesn't have to be super weird. Just ask him, and if he gets all butt hurt and doesn't want to talk to you anymore, then that sucks and you lose a friend, but better to know that now than when you're sleeping in the room next to him.
NORM: You're absolutely right. Better to blow it up now from 400 miles away than after I've quit my route and signed a lease. All right, I'm going to call him tomorrow, not text, call.
LUKE: All right, give us a call back when you have more news. Let us know how it went, okay?
NORM: Yeah, I will. Thanks, Luke. Seriously. I've been sitting on this for two years, and I think I just needed someone to tell me to stop being a coward about it.
LUKE: Yep, well, that's a tough thing, an awkward conversation that I can understand nobody would want to have. So good luck to you. I hope it works out in your favor, whichever way that is. And now it's time for a number. Another 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 micromodal cactus silk, harvested by hand from prickly pear, which is ironic because the whole point is that nothing pricks you. Every pair is cut for the man who sits in a plastic chair in the desert for nine hours recording a podcast. That's a real use case. That's my use case. use case. Saddlesoft 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.! Okay, Megan. Megan, are you the same caller that called in a few days ago?
MEGAN: Yeah, that's me. The kindergarten teacher from down in the booth heel. I've called in a couple times now.
LUKE: Yeah, I remember you. You really flipped my opinion on kindergarten teachers.
MEGAN: Oh, God, the footskin thing. Look, I knew that was going to follow me. My friend Tammy still won't let me live it down.
LUKE: Oh, Jesus, you're the footskin chick. No, I was talking about when you were telling me how important the role of the and teacher is in teaching kids how to learn for the rest of their learning careers. I forgot about the foot skin picking thing. That is, that's gross.
MEGAN: Oh, yeah, well, I mean, both things are true. I can shape young minds and have disgusting habits.
LUKE: Yes, you can. It is a free country. So what are you calling in for tonight?
MEGAN: So my sister Crystal, the one up in Flagstaff I told you about, She called me yesterday and she's moving back to Missouri. Like next month. Just decided.
LUKE: Okay. And how do you feel about that?
MEGAN: Honestly, I'm pissed. She spent seven years up there complaining about how there's nothing to do in the boutique. How she needed the mountains, needed to be somewhere with culture, all that. And now she's coming back because, and this is the part that's killing me, she says she misses family.
LUKE: Why is that killing you. She's obviously got family in Missouri.
MEGAN: Because I've been here the whole time, Luke. I drive up to see my brother Ray every other week to make sure he's not completely falling apart. I check on mom. I'm the one who stayed. And Crystal gets to sworn off to Arizona, ignore everybody for years, and then just decide she misses us?
LUKE: Well, yeah, would you rather she doesn't decide to miss you and she stays in Arizona where she doesn't want to be and doesn't get see any of you?
MEGAN: No, I look. That's not the point. The point is she gets to just make these big sweeping decisions. I'm leaving, I'm coming back, I miss family now, and I'm supposed to what, just be happy about it, roll out the welcome wagon?
LUKE: Well, it's not that she gets to make decisions, it's that she does make decisions. You can make decisions too.
MEGAN: Okay, but no but seriously. It's not the same. doesn't have a classroom full of kids depending on her. She works remote doing graphic design. She can just pick up and go wherever. I can just leave in the middle of a school year because I feel like it.
LUKE: You absolutely can leave in the middle of a school year because you feel like it. What you do at work is your job and you can change your job at any point for any reason. So if you wanted to make those big sweeping decisions in your life, you could absolutely do that.
FLOYD: Oh, man. Okay, so this is going to sound insane, but I need you to tell me if I'm losing my mind. I'm at a rest stop outside Deming right now. And about 20 minutes ago, I was eating peanuts from the gas station, and I tasted the exit sign. I literally tasted green metal in my mouth when I looked at it. And now I can't stop thinking about whether my brain is breaking or if this has always been happening and I just never noticed.
LUKE: What does green taste like?
FLOYD: Like, okay, so it's this sharp metallic thing, almost like if you licked a chain link fence, but with this weird brightness to it. And it's specifically that highway sign green. Not like like grass green or anything. Of course. The peanuts were fine. And then I glanced up at the exit sign. And suddenly my mouth had this taste that matched the color exactly. And the fluorescent lights in the bathroom, those have a taste too. Kind of burnt coffee mixed with static electricity. I know how this sounds, Luke. I really do. But it's so specific that I can't shake it.
LUKE: Are you under the influence of any drugs at the moment?
FLOYD: No. I'm stone cold sober. I'm driving a route to El Paso. I've got a delivery at 3 a.m. I don't even drink coffee afternoon because it messes with my sleep. That's what's freaking me out. If I was high or hadn't slept in two days, I'd be like, okay, yeah, that tracks. But I'm just driving, doing my normal thing, and suddenly colors have flavors. And I'm sitting in a truck stop parking lot, wondering if I'm having a stroke or something.
LUKE: Yeah, that doesn't sound right to me. Has this ever happened to you before?
FLOYD: Never. Not once in 36 years. That's why I'm calling my girlfriend Diane. She texted me this article this afternoon about synesthesia, you know, where people's senses get crossed and they see sounds as colors or whatever. And I read it while I was waiting to load up in Tucson and thought it was interesting but didn't think anything of it. Then tonight this happens. And now I'm like, did reading about it make my brain do it? Can that even happen?
LUKE: I think that can happen. It's like a placebo effect type thing. I don't know. It sounds like it could be a placebo from what you've read and what you've been thinking about, and it could also be a severe neurological disorder. So maybe you should talk to a clinic and ask them if that's a symptom of a bigger problem or nothing to be concerned about. Because I don't know. That's a weird one.
FLOYD: Yeah, you're right. I should. The thing is is, I'm supposed to be in El Paso in like four hours, and I've been sitting here for half an hour just staring at my odometer, trying to figure out if the number seven feels smug to me. Like, does that sound crazy? I'm looking at numbers and assigning them personalities now.
LUKE: No, that one's absolutely correct. The number seven is absolutely smug.
LUKE: So you're right about that, but the tasting green street signs is a bit odd, and maybe something that you want to get checked out.
CALLER: Okay, see, that makes me feel a little better. Wait, no, it doesn't. Because now I don't know if you're messing with me or if seven actually is smug. And I've just been noticing things that were always there. God, this is making my head hurt. Should I even be driving right now?
LUKE: No, you should probably not be driving right now. You should get to a clinic and see what's up with that, because it doesn't sound like you're in a stable state, and you might be on the brink of a psychotic break.
CALLER: A psychotic break. Break? Jesus. Luke, I called for some reassurance here, not to get told I'm losing my mind. I mean, okay, you might be right.
LUKE: Yeah, I can't reassure you that what you're saying is normal behavior because it's not. I've never heard of that before. And unless you're under the influence of some strong psychedelics, I don't know that that that's a good thing. It sounds like brain issue. So I'm not saying that it's a is a brain issue. I'm saying that you should get that checked and maybe stay off the road.
CALLER: All right. All right. There's a hospital endemming. I passed the sign like 10 miles back. I can turn around. My dispatcher's going to be pissed. This delivery is time sensitive. It's refrigerated pharmaceuticals. But you're right. If I'm actually having some kind of neurological event, I shouldn't be piloting a 40-foot truck down the interstate at midnight. It's just, man. I feel fine otherwise. Like physically, I feel completely normal.
LUKE: Well, you felt abnormal enough to call a radio show about it, so that's a good sign. But you should get it checked out. That's the responsible thing to do. And you don't want to hurt anybody because you ignored a symptom of a serious issue.
CALLER: Yeah, you're right. You're absolutely right. I just, God, Diane's going to freak out when I tell her.
LUKE: Yeah, that might be one you don't tell her.
CALLER: What? You think I should just show up to a hospital by myself at midnight and not tell my wife I might be having a stroke or whatever? Come on, Luke.
LUKE: Yes, that is what I think. I think you should show up at the hospital, ask them if it's a thing. And if they say it's not a thing, carry on with your day. If they say it is a thing, then have the conversation with your wife. Because what's not going to go well is a call to your wife saying that you taste the green street sign when you eat peanuts.
CALLER: Okay, when you put it like that, yeah, that does sound insane. She already thinks I'm a hypochondriac because of that whole thing with the mole last year that turned out to be nothing. If I call her at midnight saying I'm tasting colors, she's going to think I've completely lost it. But what do I even tell the ER?
LUKE: You tell the ER that you're tasting colors and you think that's strange and you want to know if that's a symptom of a known issue.
CALLER: Right. Okay. Yeah. I can do that.
LUKE: Okay. Well, you do that, buddy, and let us know how it goes, because that is the weirdest call that we've ever had. So congratulations. Hold on. Let me find one for you. We're going to give you, hold on, hold on, hold on, hold on. All right. Well done. You have the trophy right now, and hopefully someday soon we'll get a weirder call, and then they will snatch that trophy from you. But good luck with your story. street sign mouth, and I wish you the best of luck. Everybody, this has been another great episode of Luke at the Roost, and we hope you enjoy the remainder of your evening.
@@ -0,0 +1,361 @@
LUKE: All right, welcome back. It's early Friday morning. This is supposed to be Thursday night, but I got distracted, so sue me. This is Luke at the Roost. It's your late night call-in radio show where you can call in and ask me about what's on your mind. The number, if you'd like to call, is 208-439. 5853. That's 208-439 Luke. gonna take some calls because that's what we do. So first up on the line here we have Mitch. Mitch welcome to the show. What are you calling in for tonight?
MITCH: Hey Luke, thanks for taking the call. So I'm sitting here in my garage at midnight watching a YouTube video about quantum physics and I think it might be ruining my marriage.
LUKE: Well you know if we're all being honest quantum physics is ruining everything. Right?
MITCH: No, that particles exist in like all possible states at once until somebody looks at them and then they just pick one. And I can't stop thinking about how that's exactly what happened with me and my wife tonight. We had this huge fight about whether to loan my brother money for a food truck. And it's like we were both existing in all these different versions of the argument until we actually had it. And now we're locked into this one where she thinks I'm an idiot and I'm hiding in the garage eating crackers.
LUKE: All right, well if you're thinking about giving your brother money for a food truck, you probably are an idiot. Why is everybody want a food truck these days? Everybody's got a brother that's trying to start a food truck. You have a different idea. Jesus. There's enough.
MITCH: Oh man, I know. But here's the thing. Darrell's actually a really good cook, like legitimately good. He did this pop-up thing at a brewery last summer and sold out in two hours.
LUKE: Was it a fusion?
MITCH: No, thank God. It's barbecue. Proper barbecue. He's been working on his brisket for like three years. But yeah, Angie says he's a burnout because he's had like four different jobs in the last two years. And I get it, but I also think, I don't know, man. Maybe he just hasn't found his thing yet. He's 32.
LUKE: Okay, well, I have a couple of questions. Has he asked you for money before for other ideas?
MITCH: Yeah, twice. There was this landscaping company thing that lasted like six months, houses, which yeah, that one didn't even get off the ground. But Luke, the barbecue thing is different.
LUKE: Okay, and it might be different. And my next question is, do you have the money to lend him?
MITCH: I mean, we have it. We've got like 15 grand saved up that was supposed to be for redoing the kitchen. Angie's been planning that for two years.
LUKE: Well, your brother wants to start a food truck. He's 32 years old, and you've already given him money for two failed businesses. Your wife wants to redo the kitchen. You've been saving money for that. So no. I mean, my advice is don't give your brother the money. That's insane.
MITCH: Yeah, but see, that's the thing. I didn't actually give him money those other times. I just told him I would, and then Angie talked me out of it both times. And now he's got this whole business plan printed out. He's got a truck lined up. He just needs 12 grand for the equipment and permits and wrapping it. And I keep thinking, like, what if this is actually the one?
LUKE: Maybe it is, but it's not your one. It's your brother's one. And first of all, are you sure that you would get your money back? That's an important bit. And also, anybody can put a business together, put a business plan together in like 36 seconds with ChatGBTBT today. And it doesn't really take a lot of effort or foresight to look for a vehicle on Facebook Marketplace. So it's not like he's done a whole lot of work here to get started. I wouldn't give him the money.
MITCH: I mean, you're probably right. It's just, God, you should see him when he talks about it. He gets this look like he actually believes in something for once. And I keep thinking about that quantum physics thing. Like maybe he's in this state where he could be successful or he could fail, but until someone actually gives him a shot, we'll never know which one he is.
LUKE: No, that's not how it works, sir. Particles are in a state of superposition until somebody measures them. that's not giving them a chance, right? So he could be in a state of failure and success until you see which one he does. That doesn't require you giving him money.
MITCH: Okay, fair. That's... Yeah, that's fair. I guess I'm trying to make physics justify me wanting to help my little brother. But here's what really gets me. Angie said something last night that I can't stop thinking about. She said, you're not trying to save Daryl. You're trying to prove something to your dad. four years, so that really messed me up.
LUKE: Did it mess you up because you know that it's true?
MITCH: Yeah, yeah, it did. Because my dad always said Daryl would never amount to anything, and I always defended him. Like every Sunday dinner, it was the same thing. Dad would go off about how Daryl was wasting his potential, and I'd be the one saying, give him time, he just needs to find his thing. And now Dad's gone, and Daryl's still, you know?
LUKE: Well, it's been 32 years. So Darrell's had ample opportunity to make something happen. And maybe now's the time. I'm not saying that he can't make a food truck happen. I'm just saying it's not your responsibility to buy that for him. And your wife clearly doesn't want you to. And it's more important that you support her than you support him.
MITCH: You're right. I know you're right. It's just going to kill me to tell him no.
LUKE: Well, he's going to have to learn some things to be successful about business and about life and about fundraising. suggestion to you would be to help him raise the money himself and be proud and have something to lose in this business that isn't yours.
MITCH: Huh, that's actually that might work. Like help him figure out how to pitch investors or do a Kickstarter or something. So he's got some skin in the game because you're right. If it's my 12 grand, what does he have to lose?
LUKE: Yeah man, that's an awful expensive rack
MITCH: Ha, yeah, exactly. Man, Angie's going to be relieved. I've been avoiding going back inside because I didn't want to keep fighting about it. Maybe I should actually go tell her you talk some sense into me before she wakes up still mad.
LUKE: All right, sir, you go do that. You have a wonderful night, and congratulations for reclaiming the marriage and keeping the wife happy. That's always a good thing to do. And don't give your brother the money. Help him out where you can, but make him do the work.
MITCH: I appreciate it, Luke. Seriously. I was sitting out here in my garage eating crackers and spiraling. And you just, yeah.
LUKE: All right, buddy, well, you enjoy your crackers and stay out of trouble, okay? Next up on the line we have Tamika. Tamika, welcome to the radio show. How are you tonight?
TAMIKA: Oh, man, I'm kind of losing my mind a little bit, Luke. I just spent the last three hours reading about the great attractor. And I can't stop thinking about it.
LUKE: Okay, then tell me about the great attractor.
TAMIKA: Okay, so it's this massive gravitational anomaly out in space that we can't even see because it's behind the Milky Way, but it's pulling our entire galaxy toward it at like 600 kilometers per. Everything. The sun, the Earth, all of it, is just being dragged toward this thing that's so big and so far away, we don't even know what it is. And I'm sitting here in the office at the Galaxy Diner at midnight trying to close out the register. And I just keep thinking, how does anybody just go about their day knowing this?
LUKE: Well, like you said, we don't know. We don't know it. We don't know what it is. We don't know what it's doing or why. And it's too far away for us to see. So everybody's going about their life because there's nothing we can do about it. And we don't know and probably will never know what it is.
TAMIKA: But that's what's getting me, though. Like, we do know it's there. Scientists know it's there. They can measure it. They can see what it's doing to us. We just can't see the thing itself.
TAMIKA: And yeah, there's nothing we can do about it. But doesn't that make it worse? Like, I'm supposed to go home, and my mom's asleep in front of the TV with her pill organizer on the counter. And I'm going to wake up tomorrow and flip the diner sign back on. And meanwhile, the entire galaxy is just... Just moving, just moving and not affecting your life in any way.
LUKE: But there are things that we're we know exist and they're called black holes and they do exactly that. So it wouldn't be surprising to learn that the great attractor is giant black hole that we can't see.
TAMIKA: Okay, but see, that's even worse because black holes, we at least understand the concept. They're a thing. They eat stuff, whatever. But this is different.
LUKE: How is it different? It could be exactly a black hole. It could just be a thing that eats stuff.
TAMIKA: But it's not eating us though. It's just pulling us. For Bill, millions of years. We've been moving toward this thing our whole lives. Everyone who's ever lived has been moving toward it. And we just found out about it in like the 1970s. And I guess what's really messing me up is, what else is like that? What else is just happening to us that we can't see and can't do anything about? And we're just supposed to act like it's fine.
LUKE: Well, it is fine. And lots of things are acting on us that we can't see every day. And we know that. And think about it. like an ear of corn on your plate, right? So you've got this nice dinner. You've got an ear of corn there. You can't eat that ear of corn until you've pulled it towards yourself. So it could very well just be a faraway black hole pulling us towards itself on its way to eat us.
TAMIKA: Oh, great. So we're dinner. That's really comforting, Luke. No, but okay. I hear what you're saying. Things are always acting on us we can't see. Gravity, radiation, whatever. But those things we learned about in school, you know? We grew up knowing about them.
LUKE: Yeah, we also grew up knowing about the food pyramid in school. And I don't know if you've heard about that lately, but it's completely different now because it's bullshit.
TAMIKA: Exactly. See, that's what I'm saying. They told us for decades to eat a certain way. And it turns out it was completely wrong. And now we're finding out our entire galaxy is being pulled towards something we can't identify. And like, what are? What else are they going to tell us in 20 years that completely changes everything we thought we knew?
LUKE: Tons of stuff, but the point is, at the end of the day, it doesn't really affect your life. It's not going to pull us anywhere in our lifetimes. We're talking about billions of years out into the future. We'll have killed ourselves off long before then. We're almost there already.
TAMIKA: Okay, but that's not making me feel better. You're basically saying, don't worry about the great attractor because we'll probably nuke ourselves. first. Like, is that supposed to help me close out the register and go home? And I don't know. Maybe it's not even really about the great attractor. Maybe it's about...
LUKE: That's exactly what I'm saying. I'm saying, don't worry about the great attractor, because it's not going to pose a threat for billions of years, and we're all going to be dead one way or another. And the entire human race is almost certainly going to be dead before then. So what is it about to you?
TAMIKA: It's that I can't stop thinking about my mom. She's asleep right now in front of the TV with her pill organizer on the counter, and tomorrow she's going to wake up and make coffee and ask me if I remembered to defrost the chicken. And meanwhile, we're hurtling through space toward this massive invisible thing, and she has no idea, and it doesn't matter, because what matters to her is whether I defrosted the chicken. And I'm supposed to just do that? Just go home and defrost chicken and pay the electric bill and show up for my shifts while knowing that none of it means anything in the grand scheme.
LUKE: That is correct. That's exactly what you're supposed to do because you're going to have to eat tomorrow and chicken is tasty. So I would recommend that you defrost the chicken and help your mom out. And don't worry about what's going to happen to the fate of the planet a billion years from now.
TAMIKA: But how do you just do that? How do you just decide that the small stuff matters when you know it doesn't? Like, I'm standing here in this restaurant office at 12. 12.30 in the morning, counting 20s, and making sure the drawer balances, and part of me knows that's important because rent is due, and my mom needs her prescriptions. But the other part of me is like, why are we pretending any of this is real?
LUKE: That is 100% the reason we're pretending. We know it's not real. Everything that we see and touch and smell, it's all a interpretation by our brains. We know that it's not reality. Like, that's not even a question. But we do it anyway. and we care about things like de-frosting the chicken because we get hungry and we need to eat.
TAMIKA: Oh, God, you're right. I sound insane. I'm having an existential crisis over Wikipedia at midnight when I should just be going home. It's just, I think I've been feeling this way for a while, and the great-att-tractor thing just gave me something to pin it on, you know? Like, my mom's getting older and I'm working the same job I had in college, and I keep thinking, when does the real part start? And maybe it never does.
LUKE: It starts when you decide it does. It starts when you decide, you know, what is important to you and your time here on this earth. And maybe it's figuring out deep secrets of the cosmos and putting your hat in the ring of academia. Who knows what that is to you? But I think the reason to be here is to enjoy the moments that you have here as much as you can and watch the universe do what it does. the opportunity to have been on this planet before it was eaten.
TAMIKA: Yeah. Yeah. Okay. I think I needed to hear that. I've been so stuck in my head about what I'm supposed to be doing that I forgot to just be here.
LUKE: Be here now. That's what our friend Ram Dass always said. And if you're looking for some meaning in your life beyond no meaning at all, maybe look up what Mr. Richard Albert had said. say because it was pretty interesting too.
TAMIKA: I will. I'm going to write that down. Ram Dass, write, be here now. That's actually perfect because I've literally been everywhere except here for the past three hours.
LUKE: Well, I think if you listen to most any sort of self-help spiritual guru, they're going to tell you in different words that same thing. It's to focus on the most moment in front of you because that's the only one that you have. It's the only one that matters. That's the point. So if you're worried about what's going to happen a billion years from now, then you're missing what's happening now.
TAMIKA: You're right. God, I'm going to go home and defrost that chicken and maybe actually sit with my mom tomorrow instead of scrolling through my phone while she watches her shows. Thank you, Luke. I really needed this tonight.
LUKE: Not a problem there. that's what I do. And good luck not getting eaten by the great attractor. I believe in you. I think you can, I think you're going to make it. And now we have to, I'm sorry, but we have to take a word from our sponsors. Ladies and gentlemen, this episode of the radio show is brought to you by mediocre CPAP. The makers of the CPAP machine that technically works, not life-changing, not transformative, It works in the way a lot of modern products work. If you've never used a CPAP, here's the pitch. You strap on a face mask and let a small plastic appliance try to keep you alive at night. It's romantic. It's like sleeping next to a tiny leaf blower. Mediocre is fine. And that's not an insult. That's honest. It's the device that turns, I sleep like garbage into... I kind of slept like garbage. Half the night it's like, pst. Pst. Because the seal isn't perfect.
LUKE: And now you're doing arts and crafts at two in the night, trying to tighten straps like you're securing cargo. Then the humidifier runs out and the machine starts blowing desert air directly into your skull. Great. Love waking up with a throat that feels like it's been sanded with a belt sander. And yes, there's an app. Of course there's an app. It gives you a sleep score like this is a game show. But here's what I'll say. When it's dialed in, when it's not leaking, squealing, or drying you out, It does help. It's not a miracle. It's a plastic compromise that keeps you from waking up, feeling like you got punched by your own biology. Use code breathe for 15% off. Mediocre CPAP. Not perfect. Not elegant. Just trying. A very special thanks to Mediocre CPAP for sponsoring this segment of the show. And next up on the radio show, we have Rodney. Rodney, what's happening? What are you calling in for?
RODNEY: Hey, Luke. So, my wife's sister told me at Thanksgiving that Marie deserves better than a guy who still works with his hands at 57. And I haven't been able to get it out of my head since. Three weeks now.
LUKE: Well, you should be grateful that you can still work with your hands at 57, sir.
RODNEY: Yeah, I mean, I am. I've been doing electrical work for 34 years. I'm good at it. But the way she said it, Luke, like I'd failed somehow.
LUKE: Yeah, well, she's a bitch, and she's probably not working with her hands or working with anything, and she's probably going to die at 62. Well, you enjoy a long, happy, healthy life because you've remained active, and you're working with your hands.
RODNEY: Jesus, Luke. I mean, you're not wrong. She works in HR for some insurance company, spends all day at a desk. But here, Here's the thing that's eating at me. Marie didn't defend me. She just stood there. And ever since then, she's been...
LUKE: She's been what? Because an electrician is not a, um, it's a pretty respectable trade. I think most people, um, it's not a trade that's looked down on generally.
RODNEY: No, you're right. It's not. I make decent money. We're fine. But Marie's been different since then.
LUKE: Well, have you talked to her about it? Because it could be something completely different.
RODNEY: That's the thing. I haven't. I've been pulling overtime all week on this hospital job down in Lordsburg. Just staying away, I guess.
LUKE: All right. Yeah, there is that big hospital in Lordsburg. So I guess you're going to be busy for a while.
RODNEY: Lordsburg, yeah. Should wrap up in another week or two. But I come home tonight and she's already in bed at 9.30. Didn't even kiss me good night, Luke.
LUKE: Maybe she's dealing with a little bit of depression, or there's something going on in her life that she needs your support for.
RODNEY: You think that's it? I mean, her mom's been having some health issues. Nothing serious. But Marie's been driving over there a lot more. And I've been so wrapped up in my own head about this, about what her sister said about feeling like I'm not enough, that I haven't even asked her what's going on.
LUKE: Well, I would recommend you do that, and if she's spending more time going to go. going over to her mom's house and talking to her sister about you and not defending you in public, there might be something going on there, and it probably has nothing to do with your profession.
RODNEY: Oh, God. You think she's been talking to her sister about me? That whole family. They get together and it's like this echo chamber. Her sister plants one seed.
LUKE: And look, you're right. Hey, man, I'm not saying I'm right. I'm just saying that's a pattern of behavior that would make me her intentions.
RODNEY: No, I hear you. And honestly, that's what's been eating at me. It's not even what her sister said. If that Marie just stood there, didn't say a word. And now, she won't look at me the same way.
LUKE: Well, she can't look at you the same way if you're pulling overnighters and not coming home and she's tired. So I recommend you talk to your wife and find out what's going on. And if something needs to change or you need to talk to somebody or what the deal is. but don't just ignore the situation. It's obviously a thing.
RODNEY: You're right. I've been hiding at work instead of dealing with it. I'll talk to her tomorrow morning. Just straight up ask her what's going on, what she needs for me.
LUKE: Good. That's what you should do. And I hope it all works out for you. And it's nothing serious. I'm sure everything's explainable. But if not, it's not. You know, just do the best you can. And sometimes things don't work out.
RODNEY: Yeah. Yeah, I appreciate it, Luke. I needed to hear that. I've just been sitting here drinking a beer on the bathroom floor. Feeling sorry for myself when I should have been having this conversation with her weeks ago.
LUKE: Yes, sir, that is correct. So go have that conversation with your wife. Good luck. And I hope you get that hospital in Lordsburg finished because that'd be great. So next up to the show, next up, we have Marlene. Marlene, welcome to the radio show. What's eating you?
MARLENE: Oh man. Okay, so I just got slapped with a $500 fine because my front door is the wrong shade of beige. I'm not kidding. My HOA sent me a violation notice today saying my door color doesn't match the approved palette. It's beige, Luke. It's B-E-I-G.
LUKE: B-E-I-G-E-I-G-E?
MARLENE: Yeah, beige is gross, and it's clearly not on the approved palette.
LUKE: Did you look at the approved palette before you decided to paint your house? when you knew that you were in an HOA?
MARLENE: Okay, yes. Technically, I looked at it. But here's the thing. I had like 40 swatches of beige that all looked identical. My husband David was standing there going, they're all the same. The guy at the paint store was like, yeah, these are basically the same. And I picked one called Desert Sand. Because we live in the actual desert and it seemed fine. But apparently it's two shades too warm, and now Karen's somebody. on the architectural review committee is acting like I spray-painted obscenities on my garage door. I teach kindergarten, Luke. I spend all day with five-year-olds, and at least they have reasonable rules.
LUKE: Yeah, well, you live in an HOA area. They never have reasonable rules. There's always a caring somebody that's complaining about what everybody else is doing. You know what my brother would do? I want to give you the advice that my brother would do.
MARLENE: What? What would your brother? do because right now I'm sitting here in my kitchen at midnight drinking wine and staring at this violation notice like it's a ransom letter. So I'm open to suggestions.
LUKE: He would comb through that HOA, the agreements there, and look through the approved colors. And then he would either do the worst paint job possible that was still within the rules, or he would actually just paint obscenities all over everything.
MARLENE: Oh my God, I love your brother. No, but seriously. I've actually thought about this. Like, what if I just went full malicious compliance? Paint the door the exact approved beige but then plant the ugliest legally allowed lawn ornaments I can find. Pink flamingos are probably banned. But what about garden gnomes? There's got to be a loophole somewhere in that binder. The thing is, though, Luke, it's not even really about the door anymore.
LUKE: Oh, there is a loophole. There is absolutely a loophole. And, uh, yeah, I think that's what you should do. You're going to have to pay a $500 fine. You might as well get a good laugh out of it. So what it's not about the door anymore? What's it about now? That you live in an HOA and you don't want to and you want the freedom of not living in an HOA? Then move.
MARLENE: No, it's God, you're right that I hate the HOA, but it's that David thinks I'm being dramatic. He came home, looked at the notice, and was like, just repaint it, Marlene. It's not a big deal. Not a big deal! We've lived here for 18 years. 18 years of me following every stupid rule, mowing the lawn to the exact height, keeping my trash cans hidden, and the one time I pick a beige that's slightly off, I get fined like I'm running a meth lab.
MARLENE: And he just, he doesn't get why I'm so pissed. He thinks I'm overreacting, which is what he always thinks, and I'm sitting here feeling crazy because maybe a normal person would just repaid the door and move on.
LUKE: I'm with you. I wouldn't repeat. I might eventually, but I would definitely make it more of a pain in the ass than it was worth to somebody for the inconvenience. And I would turn it into something fun by, but would you call it malicious compliance? I like that. It sounds like a punk band.
MARLENE: Right. Malicious compliance opening for the violations at the HOA Community Center. But yeah, that's exactly it. I want to make this FUN and feeling like a scolded child. I deal with actual children all day. I don't need my neighbors treating me like one. But here's what's really getting me. And I heard Tamika earlier talking about the great attractor thing. I know this is going to sound weird, but I think she was looking at it backwards. She was like, why does anything matter if we're all just cosmic dust?
LUKE: I also think the oposite. I'm with you on this one, because if we're, just cosmic dust and we are, then we are everything. And that is empowering in a way, and it's beautiful in a way, and it relieves some of the nonsense of the world that we get caught up in. And the importance that we put on those things that she kept saying were not real. So, um, so yeah, I'm totally with you.
MARLENE: Yes, exactly. Like, if we're all just atoms that used to be stars, Then why am I letting some committee member named Karen make me feel small over B-E-A-I? That's what I'm sitting here realizing. I've spent 18 years making myself smaller to fit into these stupid rules. And for what? So my neighbors don't complain? I teach 23 kids every year to be brave and use their voices and stand up for themselves. And then I come home and I'm worried about paint swatches.
LUKE: How do you spell beige?
MARLENE: B-E-E-I-I. Why, you going to look up the HOA approved shades yourself? But seriously, Luke, I think that's what's really eating at me. It's not just David thinking I'm dramatic. It's that maybe he's right that I've been dramatic about small stuff for so long that now when I'm actually upset about something real, about feeling like I don't have a voice in my own life. He just sees it as more of the same. Like I've cried Wolf too many times about stupid things, and now when I'm actually drowning, he's just like, There goes Marlene again.
LUKE: Well, yeah, that is being dramatic. You're not drowning because they asked you to repaint your door. That's a bit much. I am with you in that I would also be infuriated, but I would not agree to the terms of an HOA either. You made that decision when you bought the house, and you've been there long enough to know the rules. So next time, you'll not do that. And this time it costs you $500. And you're just going to have to deal with that and move on and if it pisses you off this much, then leave the HOA. Sell your house and find some place where you have more ability to do what you want.
MARLENE: Okay, yeah, you're right. Drowning was dramatic. See, this is what I do. But Luke, we can't just sell. David's got his workshop in the garage. All his tools set up exactly how he likes them.
LUKE: Well, let me tell you this. David would love to move and have a new garage to set up exactly the way he likes it. Um, so that's not an issue.
MARLENE: Oh, God, you're totally right. He'd love that. He complains about that garage all the time. Says it's too small, the electrical's weird.
LUKE: Well, there you go. You got a couple of options here. You can paint the door, you can move. Uh, or you can be maliciously compliant. Those are your options. Pick one of them and move on.
MARLENE: Okay, so here's what I'm thinking. And tell me if this is insane. I paint the door the exact approved beige, right? But then I plant the most aggressively colorful, technically allowed by the bylaws flower garden you've ever seen. Like, I'm talking, sunflowers, zinias. Those bright orange California puppies.
LUKE: Well, I'd go with cannabis myself, but you've got to follow the rules. So learn what those rules are and then exploit them to the best of your ability.
MARLENE: Oh my God, Luke, I teach kindergarten in the front yard. But yes, this is exactly what I'm talking about. I need to actually read those bylaws instead of just being scared of them. I bet there's all kinds of stuff they can't regulate. What about garden gnomes?
LUKE: Why do you teach kindergarten in your front yard?
MARLENE: What? No, I'm saying I can't grow cannabis because I'm a kindergarten teacher. Like, that's my job. Parents strive by my house. Can you imagine? Oh, look, there's Miss Marlene's house with the weed plants. I'd be fired, before the first harvest.
LUKE: Yeah, but you'd probably be less dramatic about it.
MARLENE: Okay, ouch. But fair. You know what?
LUKE: What?
MARLENE: You're right. I'm being dramatic about the beige. Like, it's just paint. I could fix this tomorrow for 40 bucks in a Saturday morning, but instead I'm sitting here at midnight in my underwear, drinking wine and plotting revenge gardens like I'm some kind of HOA freedom fighter. David's going to wake up and I'm going to wake up, and I'm going to be a day be out there measuring the exact legal height for a flagpole or something.
LUKE: What color are the underwear?
MARLENE: Are you serious right now? They're gray, Luke. Old gray underwear and my husband's work shirt. This is what peak rebellion looks like at 47. But okay, real talk. Why does this bother me so much? Like you're right that I'm being ridiculous. But I'm furious about this.
LUKE: Yeah, I would be furious about it too. Unreasonably furious. And I think it's because it's somebody that's trying to control your life in a manner that's unreasonable. And it is unreasonable. But those are the rules that you agreed to. And you knew that well before you moved in. So it's on you to fix it. But I understand why you're furious. It's because you're losing some agency there. It's your house. You pay for it. You pay a lot for it. You pay the taxes on it. And then they tell you what color you can paint your door. That is infuriating. So you're justified in that, but you also have to just deal with it and move on or change the situation.
MARLENE: Yeah. Yeah, you're nailing it. It's the control thing. And honestly, I think it's bigger than just the HOA. I spend all day at school following district policies. What I can teach, how I can teach it. What words I can say to a kid who's melting down.
LUKE: Yeah, and I feel for you in both respects. because when somebody tells you what to do, I don't know. I don't like that very much at all. And I will lash out at anybody that tries it. So I understand. But you got to follow the rules or pay the fine or both. Or move.
MARLENE: Which is what David keeps saying and I keep shutting him down because I don't want to admit he's right. He's been wanting to get out of here for two years. He says we should buy something with actual land. No HOA. Maybe closer to his sister in Prescott. And I'm the one who keeps saying no because I'm 10 minutes from my school. I know all the neighbors. We have the grocery store right there. But God, Luke, what if I'm just staying because I'm scared?
LUKE: Well, you just told me five minutes ago that you were staying because David had his tools in the garage the way he liked him. So what's the deal? Are you staying because you want to stay or are you staying because he wants to stay? Now I'm annoyed because you're lying to me.
MARLENE: Okay, you're right. I'm sorry. I was defamation. Reflecting with the David thing because, Jesus, this is embarrassing. I think I'm staying because if we move out to Prescott or wherever, I won't have an excuse anymore. Like right now I can say I'm busy, I'm tired. A commute would kill me. I've got my classroom set up just right.
LUKE: An excuse for what? You wouldn't have an excuse for what anymore? Why would you say you're busy or you're tired because you moved to Prescott?
MARLENE: Because my mom's in Scottsdale.
MARLENE: She's been asking me to visit more. Help her more. She's got early stage Parkinson's. And my sister Jennifer lives in California. So it's on me. And right now, I can say, mom, I'm 40 minutes away. I've got school in the morning. It's too much. But if we're in Prescott? How far is Scottsdale from Prescott? Hour and a half. Maybe two, depending on trying. So it's actually farther, which sounds like it would give me more of an excuse, but, oh God, you're going to call me out on this. If we move, David's going to say, well, now you don't have the HOA stress, you don't have the commute, you've got all this free time, why don't you go help your mom more? And he'd be right. And I don't want to.
LUKE: Okay, why don't you want to?
MARLENE: Because she wasn't there for me. When I was growing up, she was always always working, always stressed, always telling me I was too much, too loud, too messy, too needy. Jennifer was the easy one, the pretty one who didn't ask for anything. And now mom needs me. And I just, I don't want to give her what she never gave me.
LUKE: I know that's petty. It's a little bit petty, but it kind of makes sense. I see how you got there. And maybe you don't want to give her. Maybe she was an awful mom that doesn't deserve it your help for all I know, but you're probably going to beat yourself up for the rest of your life if you don't make an attempt while she's still around because Parkinson's is not a thing that goes away.
MARLENE: You're right. And that's what keeps me up at night, Luke, because I know, I know, I know, in five years, 10 years. When she's worse or she's gone, I'm going to hate myself for hiding behind a stupid HOA and beige paint. Jennifer's going to fly in for the funeral. And I'm going to be the daughter who lived in Arizona the whole time and couldn't be bothered.
LUKE: Well, how deep is she into the Parkinson's? What are her symptoms right now? And does she have anybody else around in the house that is taken care of her?
MARLENE: She's still living alone. That's part of what scares me. The tremors are mostly in her left hand. Some balance issues. She fell getting out of the shower two months ago. Nothing broken, but she didn't tell me for a week. Her neighbor mentioned it. She's got a cleaning lady twice a week and Jennifer sends money for a meal service. But no one's actually there.
LUKE: Well, even if she was a bitch and you can, you should make some attempt to help her out where possible. Obviously, that can't take over your life. A lot of people go pretty far in the other direction where they do so much helping that they neglect their own needs. And that can make people sick ruined lives. So I'm sure you can find a balance where you can be helpful and be there with your mother while she's suffering because she is suffering. And hopefully, if that happens to you, somebody will be there for you in that way. At least try.
MARLENE: Yeah, you're right. I know you're right. God, I called in about paint colors. And here we are. The thing is, I think I've been using David and the HOA and all this press stuff as a distraction, so I don't have to think about the fact that my mom is alone in that house. And she's scared, and I'm angry at a 72-year-old woman with Parkinson's for things she did 30 years ago.
LUKE: Yeah, it sounds like maybe there's an opportunity there for some redemption and forgiveness and all those types of things. And, you know, you might find that you have things in common with your mom, or she's fun to be around, or funny, or funny, or whatever it is. So I recommend you make the effort and spend some time with her no matter where you live. But also, move.
MARLENE: Okay, okay. Move and see my mom. You're not letting me off the hook on either one. You know what's funny? I've been so focused on David making me move that I never even asked myself if I actually want to stay in this neighborhood.
LUKE: Well, it sounds like you've got your answer. Thanks for calling in, Marlene. I hope your mom's all right and not suffering too badly because probably Parkinson's is no fun. It's not something that you would wish on anybody, and it's not something that's easy to watch. And, um, and yeah, you got to help people when they can't help themselves. And it sounds like she's reaching the position where she can't help herself. And she needs your help, and you are close enough. So a couple hours of your week isn't going to, isn't going to hurt you. All right, Marvin. Marvin, welcome to the show. What can we do for you?
MARVIN: Hey, Luke. Thanks for taking my call. So I just got off the phone with my dad and we sat there in silence for like 20 minutes because he won't talk anymore. He had a stroke three years ago and he just gave up. And I'm sitting here reading this book about a 70-year-old woman who completely relearned how to speak. And I'm losing my mind because I know it's possible, but he won't even try.
LUKE: Well, do you know it's possible? Is it that he won't speak or that he can't speak? Because every stroke is different. Every brain is different. So he might not actually have the capacity.
MARVIN: No, he can. The therapist said so. He got two months of speech therapy right after it happened, and he was making progress. Slow, but it was there. He could get words out. They were just scrambled, you know?
LUKE: Well, imagine how that would feel to you, to your pride and your sense of self-worth and your existence on this planet to not be able to speak as a full-grown adult. Just imagine what that would feel like for a second.
MARVIN: I mean, yeah, it's got to be humiliating. But Luke, this is the thing. I teach middle school history, right? And I've got these kids who struggle with reading. And the ones who push through it, who do the work even when it's embarrassing.
LUKE: Yeah, sure. And they're kids. They're not 75 years old. You know, how old are your dad? It's a little bit different when you're a young, elastic mind and then when you're an elderly person in your, what do they call them? After the golden years, your gray years?
MARVIN: He's 72, and that's the thing. This book I'm reading, it's all about neuroplasticity, how the brain can rewire itself at any age. This woman was 70 when she had her stroke, and she learned to talk again. So I know age isn't the issue here. He just won't do the work.
LUKE: Yeah, well, that's his choice. 72 years old, and he has agency, too. If he doesn't want to do the way, if he doesn't want to relearn how to talk at 72 and struggle for the rest of his years, then he didn't have to. He's been through enough.
MARVIN: But he just sits there, Luke. We're on the phone, and there's nothing. And I know he's got things he wants to say because I can hear him breathing different. Like he's frustrated. He called me. So obviously he wants to connect, but then he won't put in the effort to actually do it.
LUKE: Well, maybe he wants to connect, but he doesn't want to talk. You know, maybe he's saying, I want you to come over and watch TV with me. Maybe he's saying, I'm scared, and I can't voice that. Maybe he's saying I need help, and there's no way I can tell anybody. So, can he write to you?
MARVIN: Yeah, he can write a little. His right hand is weak, but he can do it. He sent me a few notes, mostly just like I'm fine or don't worry. That's it. And I've tried going over there. I go every Sunday. We watch the news. I make him lunch. But the whole time I'm just sitting there thinking he could be working on this. He could be practicing.
LUKE: Maybe he could be. Maybe he can't be. And I don't think it's fair for you to say what he could be doing. Because you don't know. You didn't have the stroke and you don't have his brain. And it's very possible that the therapist was just talking shit because she read an article about somebody that had a stroke before that was able to learn to read. But everybody is in a different situation. And this man's 72 years old. And he doesn't want to learn to talk again. It's not an easy thing to do.
MARVIN: Okay, but no, hold on. I hear what you're saying.
LUKE: But I also heard Tamika earlier talking about the great attractor and how nothing matters. And that's bullshit. Things matter. The work matters. The work matters to you. That doesn't mean the work matters to him. Obviously it doesn't. So he's got his own set of values that may not match yours. And he's also got a very different perspective from his position than you have. So try and have some empathy for somebody that just lost everything and not push him to be something else, be something more. He already feels bad enough. So he's clearly humiliated by the situation as anybody would be because it's humiliating. And you feel vulnerable and you just. I mean, that's horrible. So, you know, if he doesn't want to talk, write. If he can't write, sit with him. Spend some time with him, and he's still your dad, and let him go out the way he wants to go out, you know? Because he doesn't want to struggle for the last five years of his life. That's okay.
MARVIN: That hit me. because I think I've been making this about me. You've obviously been making it about you, and it's not about you. You're the one that's healthy. You can speak, you can call a radio show and talk to me about how your father's an asshole because he had a stroke and he doesn't want to suffer. He's not an asshole.
LUKE: I never said that.
MARVIN: But yeah, you're right. I'm sitting here with this book like it's some kind of proof that he's giving up. When really, I'm just pissed that I can't fix it. I teach kids all day, eighth graders, and I can make them understand the Louisiana purchase or whatever, but I can't make my dad want to talk to me.
LUKE: It's not that he doesn't want to talk to you. He obviously wants to connect with you. Like you said, he's calling, he's reaching out, and he's sending you notes, and he wants to connect, but he doesn't want to talk, because maybe it hurts. Maybe it is just humiliating and embarrassing. It doesn't matter why. He doesn't want to, and he's, and he doesn't have to.
MARVIN: Yeah, you're right. God, I've been such a dick about this. I keep pushing the therapy thing every time I'm over there like, Dad, you could still improve. It's not too late. And he just looks at me like he's tired of having to disappoint me. And the thing is, I think part of me is scared that if he stops trying to talk, then we just won't have a relationship anymore.
LUKE: Well, that might be. And that's really sad. But if he doesn't want to talk, he doesn't want to talk. And you have to give him that freedom. And when he decides he does want to talk, he will do the work. If for some reason he gets sick of being mute and he wants more connection and he feels some optimism in his life, not just constantly being poked and prodded and pushed to do something he doesn't want to do, he might put forth the effort. So just support him as best you can without pushing him to be what you want him to be. He lost everything, and you didn't.
MARVIN: Yeah. Yeah, I hear you. I've been treating him like one of my students who just needs the right motivation. But he's not a kid who doesn't know better. He knows exactly what he's choosing. And I need to respect that, even if it sucks for me.
LUKE: Yes, you do. That's the right thing to do. So thanks for Colin, and I hope your dad is okay. And just be the best son you can to him and respect his wishes because he. He earned that. He's old enough to go out the way he wants to go out. And, uh, and yeah. And now it's time for a word from our sponsors. I'm going to be straight with you. I don't know what this product does. The folks at Bunkhouse DNS sent me a script, and I've read it four times, and I understand maybe 11% of it. Apparently, your DNS is exposed, which sounds medical. Your queries are being logged by default, which sounds a least. And Bunkhouse reroutes your lookups through encrypted tunnels, which sounds like something a villain would say in a movie before the building explodes. What I can tell you is this. I installed it. I pressed one button and a little shield icon turned green. Green means good. I know that much. My internet still works. Nothing exploded. And apparently my queries, whatever those are, are now private. Which feels like progress, even though I don't fully understand progress from all what? Bunkhouse DNS. I don't know what it does, but the shield is green, and that's enough for me. Okay, we're back. Welcome back to the show. Amber, thank you for calling in. What can we do for you tonight?
AMBER: Hey, Luke, thanks for taking my call. So I'm sitting here at the dispatch center on my break, and I just watched a guy total someone's car in a parking lot and drive off. And I ran his plates. And it's the youth pastor from my church.
LUKE: Oh, what a surprise. The youth pastor. On the sauce again. You think he was, you think he was sauced up?
AMBER: I mean, I don't know for sure, but it was a Walgreens at like 10 o'clock. And he backed into this sedan hard enough to cave the whole bumper in. Just sat there for a second, then pulled forward and drove off. I was trying to get my phone out to record it. And by the time I got the plate number, he was already gone.
LUKE: Well, are you sure it was him and his car wasn't stolen?
AMBER: Yeah, I'm sure. White Silverado. Plate came back to Danny Ortega. I know that truck. He picks kids up for youth group in it. He's got one of those coexist bumper stickers and a Calvary Baptist decal on the back window and I saw him behind the wheel. I'm like 90% sure it was him.
LUKE: Well, what is it you're going to do about the situation?
AMBER: That's why I'm calling. I've got the plate number written down right here on a post-it stuck to my coffee cup. And I keep staring at it. Like, do I call it in? Do I go to him directly?
LUKE: Well, think those things through. What happens if you call it in? And what happens if you don't? And you go to him directly. If you go speak to him about it, what outcome would you like to have happen?
AMBER: If I call it in, it goes to the cops. They track him down. He gets a hit and run on his record. Insurance fraud, maybe. The whole church finds out his wife, his daughter, the one he just baptized three weeks ago. I watched that.
LUKE: And what if Danny Ortega was a plumber from Plias? Would you feel the same way about calling the cops?
AMBER: No. No, I wouldn't. If it was just some random guy, I'd have already called it in.
LUKE: And if you go to him and talk to him, what do you think he will do?
AMBER: I don't know. Maybe he'd do the right thing and turn himself in. Contact the owner of the car. Or maybe he'd lie to my face. Or maybe he panics and runs. And then I'm the one who let him let him get away with it when I could have just done my job.
LUKE: Well, here's what I recommend you do. You know the guy and you don't want to be a snitch and you don't want to ruin somebody's life unnecessarily. So I would recommend that you try to talk to him and see what happens. And if he lies to you or if he is obviously inebriated or looks like he's in a no shape to be driving a youth van full of children around, then you call it in. But check with him first, see what happens. And, uh, for all you know, it wasn't him. It was his son driving the car.
AMBER: Yeah, yeah, that makes sense. Give him a chance to explain it first. I mean, I've known this guy for two years. He organizes the food drive every Thanksgiving. It just feels so out of character.
LUKE: Well, you never really know somebody, especially if they, uh, fall under the influence of the a drinking problem or drugs or something like that. And you can't be sure that it was him because you didn't clearly see his face. You said you only thought 90% and that's not proof.
AMBER: Right. I didn't see his face. Just the plate. And the track matches. White Silverado. Same dent in the tailgate I've seen in the church parking lot. But you're right. Could have been someone else driving it.
LUKE: Are you sure if you called that? that the police would throw the book at the guy, or would they recognize that he was a pastor and kind of give him a tap on the wrist?
AMBER: I mean, honestly, probably depends on who catches the call. Some of the guys I work with would absolutely go easier on him because he's a church guy. But if he was drunk, or if the damage is bad enough, or if the owner of that sedan makes a big deal about it, then it doesn't matter who he is. always that one cop who doesn't care if you're a pastor or the mayor, you know.
LUKE: Well, that's the right cop. You know, that's the cop that's following the law and keeping people accountable no matter who they are or what they do.
AMBER: So I can respect that. I can also respect the cop that lets everybody go because he recognizes it's going to ruin their lives. So I guess, yeah, it depends on which one you get. And neither one of them would be completely wrong. So that's a tough situation. But if this guy is drinking or has a drinking problem, if that's what caused the accident, he might not even remember doing it. You know, he could have been blacked out and totally oblivious to the situation. And if that's the case, that indicates that there's a problem and he shouldn't be on the road with kids.
LUKE: Yeah, that's what keeps eating at me. Like, if he doesn't even remember it, then what happens next Sunday when he's loading up the church van with a bunch of kids? That's the part that makes me feel like I can't just let it slide. It's not just about the bumper on some random car. It's about whether he's safe to be around those kids.
AMBER: Well, my recommendation is to go talk to him and give him 24 hours to turn himself in. And if he doesn't, then you do it.
LUKE: Okay. Yeah. That feels right. I'll go by the church tomorrow. Catch him before the food drive set up. Give him a chance to do the right thing himself. And if he doesn't.
@@ -0,0 +1,289 @@
LUKE: All right, ladies and gentlemen, welcome back to Luke at the Roost. This is the call-in radio show where you can call in and ask me your questions. If you'd like to call in, the number's 208-439-5853. That's 208-439 Luke. If you're not near a phone and you'd still like to participate in the show, you can email us at submissions.com. And with that out of the way, I just want you all to know that our street team has been hard at work spreading the word about the roost. All those roosters are out there dropping off stickers and business cards and truck stop bathrooms all across the country. So if you happen to see my face on a sticker, on an ice box in front of a loves, that's intentional. So don't remove it. First up on the show, we have caller Woody. Woody, welcome to the show. What would you like to talk about today?
WOODY: Hey, Luke, yeah. So I found a USB drive in the company truck three weeks ago, and I've been sitting on it because I'm pretty sure my foreman's been running fake invoices through our drilling operation for like two years.
LUKE: Did you look at what's on the USB key? Because it might be more interesting than that.
WOODY: Oh, I looked. I looked too much. It's not just invoices. It's spreadsheets with two sets of numbers, emails between him and some guy and me. Midland, who's definitely not a supplier, and a folder labeled backup with receipts from a casino in Ruidoso.
LUKE: Well, that's not good. What are you going to do with that information?
WOODY: That's the thing, man. I've been parked at this truck stop for three hours trying to figure that out. I could hand it to the site manager, but Big Jim's been my foreman for four years, and he got me this job when I really needed it. Or I could just toss it and pretend I never saw it, but then I'm sitting on actual fraud, and that eats at me.
LUKE: Why does that eat at you? What does it have to do with you?
WOODY: Because I'm the one who found it, you know? Like I didn't go looking for it. It was just sitting there in the glove box of truck number seven. But now I. And every time Big Jim texts me about helping with his cattle or asks me how the rig's running, I'm looking at him different. Plus my dad lost his pension when his company went under because of shit like this. So it's not just abstract to me.
LUKE: Okay, well, this is a buddy of yours, right? You said he got you to the job and he's your foreman, you work with him all the time. Maybe you should just talk to him and see what's up because it could be an explainable situation.
WOODY: I mean, yeah, I thought about that, but Luke, there's like 40 invoices from a Southwest drilling supply that doesn't exist. I googled it. And the emails, man, they're not ambiguous. There's one where he's talking about keeping the split at 6.0 and another one about making the actual equipment rentals. That's not a misunderstanding. That's a system.
LUKE: Okay, well, if holding on to this information makes you feel strange, how do you think being a whistleblower would make you feel?
WOODY: Oh, God, yeah, that's exactly it. Like, I'd be the guy who burned big Jim and everyone on the crew would know it was me because I'm the only one who drives that truck regular. I'd be done in this industry, at least around here. Word gets around fast when you're the snitch.
LUKE: if you're technically doing the right thing. Do you know what they say about snitches?
WOODY: Yeah, they get stitches. I know. And that's not even hyperbole out here. Big Jim's got friends who, look, let's just say people handle their business differently in the oil patch. But honestly, what gets me more than that is just being the guy who couldn't mind his own business.
LUKE: Yeah, it's a little bit messed up that you found a USB key and then looked to see what was on. Was there anything else on the key besides these invoices?
WOODY: Oh, there was some other stuff, like a bunch of pictures from his daughter's birthday last year, and a playlist called Big Jim's Roadhouse Mix, that's mostly George Strait and some weird country rap. But the invoices were in a folder labeled Tax Prep 2024, which is just, yeah. I mean, I wasn't snooping for this, but when you see something like that, you don't just unsee it. It's like walking in on your parents doing it. You can't pretend you didn't.
LUKE: Since you found it, you feel weird about it, and you don't want to sit on the fraud, and you don't want to say nothing. You've got to talk to Big Jim and ask them what it's about. Let them know that you know. Let them know that you found the key. You've got the evidence, and if it continues, you're going to turn them in, but give him the opportunity to turn it around. Because I think if you don't do that, you are going to be the snitch, and it's nobody's going to want to do business with you anymore.
WOODY: Oh, man, you're saying that, like, it's a choice. Like, I'm just going to walk up to Big Jim, who's got a belt buckle bigger than my fist and a temper to match and say, that's not how this works. That's how you end up in a ditch outside of Deming. But, okay, fine.
LUKE: Well, that's why you're smart about it, and you give a copy about it to a trusted person. If anything happens to you, that copy gets out, and you let Big Jim know that you've got the evidence. Somebody else has the evidence, and if he doesn't cut the shit, you're going to turn him in.
WOODY: Jesus, Luke, now we're talking about insurance policies and dead man switches like I'm in a mob movie. But I mean, yeah, that makes sense.
LUKE: Well, you're talking about conspiratorial fraud in an oil patch with a guy named Big Jim. You're already in a mob movie, buddy.
WOODY: Okay, fair point. So what? I give a copy to Angela? She already thinks I'm too paranoid. This is going to really seal the deal. Hey, babe, hold on to this USB drive and if I turn up missing, mail it to the feds. That's definitely getting me laid.
LUKE: That is definitely getting you laid. Chicks love danger, and you're a dangerous man. So do the right thing, be smart about it, and, you know, let them know that it's not going to stand and you can't be part of it.
WOODY: You know what? You're making this sound way cooler than it feels. I'm sitting here in a Flying J parking lot eating beef jerky and having an existential crisis, and you're turning me into some kind of whistleblower action hero. But all right, all right, I'll make a kind of. I'll make a I'll give it to Angela, who's either going to think this is the hottest thing I've ever done or the stupidest, and I'll have a conversation with Big Jim. A very careful conversation, probably in a public place.
LUKE: There you go. Now you're being smart, and it can be the hottest thing you've ever done and the stupidest. They're not mutually exclusive. So do the right thing. You've got a plan now. Execute it, and you're going to be just fine.
WOODY: You know what, Luke? I called in here thinking I was just going to vent and maybe get told to forget about it. And instead, you've got me planning like I'm about to take down the Gambino family. But honestly, yeah.
LUKE: Hey, you don't know. This could be the tip of the iceberg, but like you said, Big Jim is your buddy. He's your friend. He's a guy you want on your side. So you can frame this in a way that you're really doing him a favor. Like, look, this is going to get out one way or another, and I'm giving you the opportunity to keep the lid on that by straightening out your act.
WOODY: Oh, man. That's... that's actually smart. Like, hey, Jim, I stumbled across something, and I'm bringing it to you first because I respect you. But this can't continue. Make it about loyalty instead of accusation. Give him a chance to fix it before it blows up in everyone's face. The thing is, I don't even know if he's the one running this, or if he's just signing off on stuff someone above him is pushing down.
LUKE: Either way, you're right. It's going to come out eventually. It is about loyalty because he is your buddy. And if he's just, it wasn't about loyalty, you already would have turned him in. So that's the truth. Stick with it.
LUKE: Do the right thing. And maybe flee the country. Okay, so step one, make copies. Step two, give one to Angela with the sexiest, if I die, avenge me speech I can manage. Step three, talk to Big Jim like a reasonable human being. Step four, have a valid passport ready just in case. Got it.
WOODY: You know what, Luke?
LUKE: What, Woody?
WOODY: I feel like I just went from guy who's going to throw this in a dumpster and pretend he never saw it to guy with an actual plan in like five minutes. I don't know if that's good judgment or if you just talked me into something incredibly stupid. But I guess we'll find out. Worst case scenario, I'm calling you back from Mexico in a couple weeks.
LUKE: All right, Woody. Well, thanks for the call. We hope it all works out for you. And if it doesn't, we will avenge you. You have a wonderful night and stay safe out there. Tamika, Tamika, welcome to the show. What would you like to talk about tonight?
TAMIKA: Luke, hey. Okay, so, the sheriff just showed up at my neighbor Rose's house, looking for me because of some stupid warrant from last summer. And she? Covered for me. But now I'm sitting here thinking I might have just made everything worse for her.
LUKE: Well, yeah, if you're dragging somebody else into your legal problems, you're. You're obviously a burden to your friends and society. So what is it that you did?
TAMIKA: It was a poker game, man. Last August, over at this guy Daryl's place off Route 9. I won $400. And apparently, the game got rated like an hour after I left. Illegal gambling or whatever. I didn't even know that was coming. I just took my money and went home to my kids.
LUKE: And how is there a warrant for your arrest? How can they prove that you were at this illegal poker game? First of all, nobody. cares about 400 bucks in an illegal poker game. But what makes the fuzz what got them on your tail?
TAMIKA: That's what I'm saying. I don't even know how they got my name. Best I can figure is someone talked. Or maybe Darrell had a list or something. He was running these games pretty regular like every other weekend. But I got the notice in the mail back in September, said I had to appear for some misdemeanor thing. And I just, I didn't I was working double shifts. The kids had school starting. And honestly, Luke, I thought if I ignored it, maybe it would just go away.
LUKE: Well, you know damn well that you can't ignore a warrant or a deposition or anything from the court, and it's going to go away. I mean, that's stupid. But I'm not a lawyer. Let me put that out there. I'm not a lawyer at all. But I'm pretty sure somebody just saying your name doesn't put out a warrant for your arrest.
TAMIKA: No. You're right. I had to sign in when I got there. Daryl had this whole thing like a clipboard at the door. Said it was for insurance purposes, which I thought was weird. But whatever, I just wanted to play cards.
LUKE: What the hell? You walked into an illegal poker game, and the host of the game had you physically write down information that's identifiable? That's absurd. Who would do that? Why would you do that?
TAMIKA: I know. It sounds insane now, but at the time it felt, I don't know, legit. Like, it was true to make it seem professional or something. There were like 12 people there, half of them I knew from around town. It didn't feel sketchy. It just felt like guys playing cards on a Saturday night. I wrote down my name and phone number like an idiot and didn't think twice about it.
LUKE: Well, you know what? I think, skipping, skipping court, that's a crime. But having your name on a list isn't necessarily. I'm sure if you got a good enough lawyer, you could get yourself out of that one pretty easily, and you should just go to court and see what it even is. Because do you know that this is why they're looking for you? Has anybody from the police department said it's for illegal gambling?
TAMIKA: No, actually, the deputy didn't say anything to Rosa, except they were looking for me about a warrant. I'm just assuming it's the poker thing because that's the only legal. trouble I've had. But you're right. I don't actually know for sure what it's about. I never opened the court notice all the way. I just saw failure to appear and my stomach dropped. And I shoved it in a drawer.
LUKE: Okay, well, none of what you have told me thus far in this call has been very smart. So I'm questioning your intellectual capacity, but I'm going to tell you right now, you got to go to the court and see what this is about and get it cleared up, because it's not going to get better if you continue to hide. from the sheriff. It could be nothing. It could be, uh, it's, it's probably unrelated, but you know what's worse than illegal gambling? You won $400. That's tax evasion. That's way worse. They will come after you forever for evading tax on $400. So you clean this up.
TAMIKA: Oh God, I didn't even. I didn't report it on my taxes. Luke, I just bought groceries and shoes for my kid. I didn't think about the IRS. Jesus Christ, is that really worse? Because now I'm spiraling here. You think that's what this is about?
LUKE: No, I don't think that's what this is about. I think this is about something completely different. Because honestly, nobody cares about your $400 local poker game. There would have to be something else going down there, and you're not the one that they're after. If they're staking out a high-stakes-if it's a Molly's game situation, they're not after you. and they're not looking for you. They might be trying to get information out of you about someone else that was there. That I could see, but nobody cares about your 400 bucks. They might try to use that for leverage. But, yeah, and since I've identified that you're not the brightest, do not report this to the IRS.
TAMIKA: Okay, okay, so you think they want me to talk about somebody else who was there? Because there were a couple guys I didn't recognize. One of them had a real. nice watch was throwing around hundreds like it was nothing. Daryl seemed to know him, though. You think that's what this is? They're trying to flip me on someone bigger?
LUKE: I have no idea what this is because you didn't read the fucking document they sent you that told you what it is. So maybe read that. It might have some indication. I'm just trying to give you a game plan for if you do go into the sheriff's office and they have questions because that would be their intention. It's not to come after you, a mother of however many kids you're all. mother of for $400 in a home poker game.
TAMIKA: Two kids. And yeah, you're right. I need to dig that paper out of the drawer and actually read the whole thing. I just panicked when I saw Warrant and my name and I couldn't deal with it. But now Rosa's spooked. The deputy's been to her house and I'm sitting here making tea at midnight like that's going to solve anything. I'll look at it tomorrow morning. First thing.
LUKE: That's good because otherwise you're spiraling into a life of crime and you're bringing down the people that are around you with you. And it's, uh, you're a detriment to society. And nobody wants that. So, so, uh, take care of this situation. And if you've got to stop playing poker at this poker game, I would recommend doing that for now. And, uh, maybe play online or actually go to a casino. There are plenty of casinos with poker rooms where you can go play legitimate poker, uh, for stakes that you're, uh, that you're, that you're, uh, that you're comfortable with.
TAMIKA: Yeah. Yeah. No. I haven't been back since. That was six months ago, and I learned my lesson. I'm not cut out for that kind of thing. I get nervous just thinking about it now. You're right, though. I need to stop hiding and just deal with it like an adult.
LUKE: That is correct. Thank you for the call. We hope it all works out. And congratulations on your big poker win. Thanks for the call again, Tamika. Now it's time for a word from our sponsors. Today's show is brought to you by Overwhelmed VPN. Worried about online privacy? You should be. You should be worried about everything.
LUKE: I'm talking about overwhelmed VPN, the only VPN that makes you more anxious about your digital footprint. While other VPN's claim to protect you, overwhelm VPN sends you hourly notifications about who's tracking you, how many governments are watching, and which of your high school classmates Googled your name this week. Browse the internet while our proprietary fear engine tells you exactly how many corporations you owe your soul to. Plus, our servers are located. in international waters on a decommissioned oil rig, so even we don't know where your data is. Try Overwhelm VPN free for 30 days if you can handle it. Use code they watching for 20% off. Overwhelm VPN because ignorance is bliss, but you're not here for bliss. All right. Make sure you're protecting your online. identity with overwhelmed VPN. Next up on the call roster here, we have Clarence. Clarence, welcome to the show. How's your evening going?
CLARENCE: Oh, man, it's going. You know, got the space heater cranked. Doing paperwork I should have done three weeks ago. So living the dream. But hey, Luke, I got to ask you something.
LUKE: All right, hit me with some funny shit.
CLARENCE: So last Saturday, I'm at my buddy Danny's barbecue. We've known each other 20 years, right? Hunt together, help each other move, the whole deal. I go inside to grab a beer, and I hear him out by the grill, telling people that I can't even keep my own marriage running, much less an AC unit. And here's the thing. He's not wrong about the marriage part. Been divorced two years. But man, that landed different than the usual shit we give each other. I don't understand.
LUKE: that bother you, sir. I mean, okay, so normally we bust each other's balls all the time, you know? That's just what we do. But this wasn't to my face. And how well do you know your way around an AC unit?
CLARENCE: What do you mean? I've been running Clarence's heating and cooling for 16 years, Luke. I know my way around an AC unit just fine. That's not the point.
LUKE: What is the point, sir? Your buddy said some shit behind your back and you didn't like that?
CLARENCE: No, it's not just that. It's the way he said it. Like he was of it. Like he'd been waiting to say it. And here's the thing. Danny's my best friend.
LUKE: Okay. How do you know he was talking about you? I mean, there's not that many clearances around, but there could be more than one clearance.
CLARENCE: Oh, come on, Luke. We were at barbecue. literally in the next room. He knows I run the AC business in town. And when I came back out with the beer, everyone got real quiet and looked at their shoes, you know?
LUKE: Okay. Is it a possibility that you may be a cuckold?
CLARENCE: What? No, Jesus, Luke. I'm divorced. There's no cuckolding happening.
LUKE: Okay, so you can't keep a marriage together and you've been divorced for a long time. It's not a very good from your friend. So I don't understand why this is bothering you much. Talk to your buddy and figure out what that was about. Maybe he was drunk. Maybe he was tripping on acid. You don't know.
CLARENCE: I mean, yeah, the divorce was three years ago. And you're right. It's not even a creative insult. But that's what's eating at me, Luke. It wasn't creative. It was just true.
LUKE: Okay. So there you go. You're bothered by it because what he said was the truth and you knew it. So what are you going to do about that? It's not his fault.
CLARENCE: No, you're right. It's not his fault. I just, I've been sitting here all week thinking about it, and I keep coming back to the same thing. Like, why would he say that? We've been friends for 20 years.
LUKE: Why don't you ask him that? Why are you calling the radio to ask me why he would say the truth about your life?
CLARENCE: Because I'm afraid of what he'll say, all right? I'm afraid he'll tell me he's been thinking it for. years that everyone's been thinking it, that I'm the guy who can fix everyone else's AC, but can't keep his own shit from falling apart.
LUKE: And I heard Woody earlier, the guy with Big Jim and the fraud thing, and he's going to go confront his friend directly. And that's the same advice I'm going to give to you is to go confront your friend directly, because if your friend's feeling that way about you and has been for some time, maybe that friendship is not worth keeping.
CLARENCE: Yeah. Yeah, yeah, you're right. I just, sitting here in this office at midnight, you know, 20 years of friendship, we were at each other's weddings. His kid calls me Uncle Clarence.
LUKE: Well, that's a bit weird. That sounds a little creepy. I don't know what to tell you, Clarence. This doesn't sound like much of an issue to me. Go talk to your friend and figure out what that was about because people spout off shit all the time.
CLARENCE: Whoa, hold on. The kid thing isn't creepy, Luke. That's just what kids do down here. All my buddy's kids call me uncle. That's normal. But you're right about the rest. I'm making this bigger than it is.
LUKE: Well, that is not normal, sir. I'm here to tell you that Uncle Clarence sounds creepy. So go talk to your friend and stop being such a baby. Jesus, it's with these people. Next up on the line, we have Sandy. Sandy, welcome to the show. How can we help you? I hope you have a better story for us than Clarence did. Jesus. What about that guy? Uncle Clarence over there? I'm creep.
SANDY: Oh man, no. I'm not calling about my feelings getting hurt at a barbecue. I can promise you that. So here's the thing. My boss gave me until next Saturday to sign off on a bunch of fire safety reports that say we did drills and equipment checks that absolutely never happened, like not even close. home, and I'm supposed to just put my name on paperwork that says we're all good when we are very much not all good. And I know what you're going to say. Don't sign it, report it, whistleblow. But what I actually want to talk about is how this is exactly the kind of thing that's been keeping me stuck in this town for two years longer than I should have been here.
LUKE: Okay, well, I don't understand that last part, and you're right. I am going to tell you to whistleblow like that. I mean, if something were to happen, then you'd never be able to live with yourself afterwards if you did not whistleblow. But what is it about this situation that's been keeping you stuck in this town?
SANDY: No, it's not that this specific situation has been keeping me stuck. It's that this is like the perfect example of how everything works here. Like, my girlfriend left for Tucson two years ago, and I didn't go with her because I had this job and it felt responsible, you know? stable, and now I'm sitting here at one in the morning in an empty building, being asked to compromise myself just so this place can keep its license and keep operating. Which is the same logic for everything. The county's broke, the state fundings a joke, so we all just agree to fudge things a little bit here and there because otherwise the whole thing falls apart.
LUKE: Yeah, well, there's a lot of fudging going around and a lot of businesses. I've never worked anywhere where at some point I wasn't asked to compromise myself. So that is part of capitalism, I think. But when it's a safety issue, if it really has you concerned and people could be hurt from your lack of, lack of, what's the word, I'm not looking for, integrity, then you've got to do something about that. Because if you don't, then you suck. You might as well kill the people yourself, right? you have to report that.
SANDY: Yeah, I know. I'm not signing it. I already decided that. I called my supervisor on Friday and told her I wasn't comfortable with it. And she gave me this whole speech about how we're all doing the best we can with what we have. And how if I make a big deal out of this, the state could shut us down. And then where would these residents go? Which is probably true, honestly.
LUKE: It may be true, but you know what would not happen? Those residents would not die in a fire because they didn't shut down a place that was unsafe.
SANDY: Right, exactly. And that's the thing. I'm not even wrestling with whether to sign it anymore. I'm not going to.
SANDY: What I keep thinking about is how I've been making these little compromises for two years, telling myself it's because I care about the residents, or because the way. work matters, whatever. But really?
LUKE: Yeah, I sympathize with that situation. I think we've all been in similar positions, and I can understand how you feel sort of compromised because you have compromised, but that doesn't mean you have to continue, right? You've come to this realization. You've had this moment of clarity. And this is how the world works. That's every single place I've ever worked, has asked me to compromise myself in some way. Now, they're not all safety issues, but it's always somebody saying, well, you got to do this for us, even though I know that it's wrong. And, and, you know the thing? You can compromise for them over and over and over. You can be as loyal as, I don't know, a golden retriever. And when it comes down to it, they will throw you under the bus in a second.
SANDY: Oh, I know. I saw it happen. to the last night shift supervisor. She tried to raise a stink about the staffing levels, and suddenly she was no longer a good fit. Poof, gone. And I'm sitting here in the break room. Boots propped up on a folding chair, picking at a Diet Coke label, and the only light on in the whole building is this one and the hallway nightlights.
LUKE: Yeah, and it's good to do the best you can and care because in some respects, you're obviously doing the right thing and helping a lot of people. And that's good. But the company doesn't care about you. And it doesn't matter what company it is because the company cares about the company. And if you're not an owner of that company, they do not give a shit about you. They might pretend to, but that is performative. That is HR. That is marketing. So make sure you know that when you're going into this stuff. The best work you can, but at the end of the day, you are on your own. And don't compromise.
SANDY: Yeah, and that's what my girlfriend kept saying before she left. She was like, Sandy, they're using your sense of responsibility against you. And I didn't want to hear it then, because it felt like she was saying the work didn't matter, but that's not what she meant. She meant exactly what you're saying. They'll take everything you're willing to give and then act surprised when you finally say no to something.
LUKE: And honestly? You know what? I used to work for a company called Salesforce. Maybe you've heard of them. The CEO there, his name's Mark Beniof. And when I worked there in like 2015-ish, it was the culture of the company. It was a very employee-forward company. there's all the snacks you could eat and Ohana culture. Like they were all about Hawaii and Ohana and everybody's family here. And you know what's happened at Salesforce now? They laid off 40% of the workforce and all of that performative bullshit about philanthropy and fixing hospital wings has turned into, how can I suck the dick of President Donald J. Trump so that I can get the tax rebates that he was promised? And this dude Benning, off, he's out there making jokes about ice and saying, I should come to San Francisco and clean it up. And it's the complete antithesis of what the company was founded on because all of that is bullshit. So when a company is in a position to be playing public opinion with their philanthropy and good works and employee first culture, take that with a grain of salt because when the tides turn, they turn with them.
SANDY: Jesus. Yeah, I mean, this place never even pretended to be that. We've always been broke and scrambling. But you're right, it's the same thing just at a different scale. They'll use the we're helping people angle to get you to cover shifts with no notice, to ignore safety violations, to take on extra residents when we're already understaffed.
LUKE: Yeah, when I was there, they had. this big conference every year and they get will I am and whoever is popular at the time to come in and give a speech about change in the world or some bullshit. And when I was there, that person that they paid hundreds of thousands of dollars to come speak was Hillary Clinton. Hillary Clinton came to the company and all support behind Hillary Clinton. But you know who we're in bed with today? Mr. Donald J. Trump. And that's the thing.
SANDY: At least with... those big companies. You know it's all fake from the start. Here, it's worse in a way, because everyone actually does care about the residents. Like, my co-workers really do give a shit, but the organisation itself is still using that against us. If you don't sign this, if you don't stay late, if you don't cover that shift, these vulnerable people suffer. And that's The truth is, I should have left when gendered.
LUKE: Yeah, and I sympathize with your situation, because that is true. Most of the people that you work with that are subordinates, that are employees of the company doing the actual work, they do care. They do want to do their best work. They do want to take care of the clients. But the people that make the money, they don't give shit. And as soon as they can find a way to do it cheaper, regardless of if it's better or more effective, they're going to do it cheaper and they're going to cut you. you. So don't stay up all night for the company. Or, I mean, even for the residents, I would say, once in a while, that's okay, but you can't burn yourself out or give up relationships or decide where you live for that. That's too much. And I'm glad that you've come to this realization now before they're throwing you to the curb. So yeah, find yourself a new position, work for yourself. Be your own. agent.
SANDY: Yeah. And the stupid thing is, Jen's been telling me for months there's work in Tucson. She's managing a coffee roaster now making more than I do here. And she keeps saying they need people. But I've been telling myself all these reasons why I can't leave. The residents need consistency. My co-workers are already stretched thin. I know this place inside and out. But Really?
LUKE: Well, it sounds like it's time for you to move on. And you did it for as long as you could. You did the best you could. And when it came time to compromise your morals and your values, you stood up for yourself. And you said, I'm not doing this. And you stood up for your clients in the same way by not allowing, not allowing them to be in an unsafe situation. So you can feel proud of that and sleep good tonight because you did the right thing. And now it's time to move on.
SANDY: Yeah, you're right. I'm not signing those reports. And honestly, I think I've known since Tuesday I wasn't going to. I just needed to.
LUKE: Well, good. I wish you the best of luck, wherever you end up, and remember the lesson that you learned here, because that is the way that it is. And make sure that you are completely aware of the compromises that you're making and their effect on your well-being. and your life, because nobody else is thinking about that for you, even if they tell the world that they are. They're not.
SANDY: Yeah. Thank you, Luke. Really? I'm going to call Jen when I get off shift. And hey, maybe I'll call you back in a few months from Tucson and let you know how it went.
LUKE: All right. Thank you for the call, Sandy. And I hope that it all works out for you. Associate employee contemporaries at Salesforce aren't going to be happy about that one, but fuck them. Now it's time for a word from our sponsors. Stressed, sore, skeptical, doesn't matter. Try placebo leaf, the CBD brand that may or may not do anything but cost enough that you'll convince yourself it works. Our tinctures, gummies, and topicals contain premium hemp extract, and your desperate need to feel better without addressing root causes. Is it the cannabinoids or is it the ritual of self-care? Who's to say? Placebo Leaf is THC-free, vegan, organic, and backed by studies we vaguely reference but don't link to. Take our gummies before bed and sleep great, probably because you retired anyway, but you'll credit us.
LUKE: Available in flavors like calm and focus. Concepts we've bottled and marked up for 100%. Visit Placeboleaf.com and use code chill. for 25% off. Placebo leaf, wellness is a feeling, and feelings can be purchased. All right, I am calmed down now after our last call. That one got to me because the exploitation of people in that way really pisses me off. And I've seen it too many times at too many companies now. So let's move on. Next up. On the line we have Lakeisha. Lakeisha, welcome to the show. What would you like to talk about today?
LAKEISHA: Hey, Luke. Yeah. So I went to my high school reunion tonight and nobody remembered me. And I mean, like I was a ghost, four years at that school. And I walked in and got blank stairs.
LUKE: Okay. Well, how, what was your participation in school? Were you in any, like the band or sports? Or what, what was you? was your school like?
LAKEISHA: Oh man. I was everywhere. Volleyball team. Choir second soprano. Thank you very much. Worked the concession stand at every single football game. You name it. I was the one who always had the extra pen in geometry. The one who'd drive people home when their car broke down. I was.
LUKE: And tonight? How many concession stand workers do you remember?
LAKEISHA: Okay. Fair, but I'm not talking about, like, random people I stood next to once. There was this girl, Brittany, who copied my geometry homework every single day for an entire semester. We sat together. Every day.
LUKE: Okay, well, I will admit that this scenario sounds odd, that you could have been that involved in school at the time, and nobody remembers you now. Did they remember each other? Was this like a spike-the-punch situation where everybody was... set up for a high school reunion orgy? Like, what was the vibe of the reunion?
LAKEISHA: Oh, it was a Spike the Punch situation, though I wish it had been. At least then I'd have an excuse for why nobody remembered me. No, it was just polite. Like, everyone was being nice. Too nice. The kind of nice that makes you feel like you're being humored.
LUKE: Well, yeah, it was a high school reunion. Nobody wants to be there. Everybody's being humored. Um, that's, that's the deal. But it sounds odd nobody remembered you. Are you significantly different now? Do you look different? Uh, uh, uh, this is your voice changed? Uh, what, what were you like in high school versus what you're like today?
LAKEISHA: I mean, I've got different hair now. I cut it short about a year ago. But it's not like I had plastic surgery or something. I'm still me. Maybe a little heavier. But who isn't after four years?
LUKE: Define a little.
LAKEISHA: Okay, maybe 30 pounds. Fine. But Luke, come on. I'm not unrecognizable. And it's not just how I look.
LUKE: I'm not trying to shame you in any way. I'm just trying to understand how this could happen. And if somebody has significantly changed their physical appearance in some way, I mean, that's one way it could happen. You say it's not just how you look. How else is it?
LAKEISHA: It's... I don't know. Maybe I'm quieter now. In high school, I was always the one organizing stuff, you know, making plans, pulling people together. Now I deal blackjack at the casino five nights a week, and I barely talk except to call bets. I watch the same people lose the same money every shift, and I just...
LUKE: Okay, well, I've got a question for you now. It's a strange thing that you're describing for sure. It would make sense that at least some of those people would remember remember you from your days in high school. But why do you care if your high school reunion buddies remember you today? What does that matter? How many of them do you still interact with? I mean, if you stayed close with anybody from high school, they would obviously remember you. So you must not be close with any of them. And what does it matter if they remember you or not? High school was over a long time ago.
LAKEISHA: Because I don't know. I guess I needed proof that I was real back then. that those years actually happened and meant something. And not close with anyone from high school, you're right. But when you're standing there, and someone you ate lunch with every single day is looking at you like you're a stranger.
LUKE: Yeah, it's odd, and I see how that could feel pretty terrible for a little while. But then, I mean, take that emotion out of it for a second and think about the situation and how much that actually affects your life. It doesn't matter that those years mattered. It was high school. They didn't matter. And the ones that matter are the ones that are now.
LAKEISHA: You're right. I know you're right. But that's what scares me, Luke. What if it's happening now, too?
LUKE: Well, are you close with anybody?
LAKEISHA: My grandmother. That's pretty much it. I live with her. Actually. I'm sitting in her kitchen right now with this stupid name tag still on my sweater.
LUKE: Okay. Well, maybe this is a wake-up call to you to do more with your life. I assume you're working at the casino because it's the best job you could get with what you had at the time. What did you do after high school? Did you get a degree or what trade did you want to go into?
LAKEISHA: I did a year at community college, tried to do nursing. But my mom got sick, cancer, and I dropped out to help take care of her. She died about three years ago. After that, I just... I needed money, you know?
LUKE: Yeah, and that makes a lot of sense. You got to do what you got to do for the money. But what I'm asking is, if you did not need money, what is it that you would be doing?
LAKEISHA: I was good at organizing things, events, fundraisers, that kind of stuff. In high school, I ran the whole volleyball banquet senior year, got local businesses to donate, made it this whole thing. I liked making people feel...
LUKE: Making people feel how?
LAKEISHA: Seen, I guess. Like they mattered. That banquet. I made sure every single player got recognized for something specific, not just the starters. I spent weeks on it.
LUKE: Well, that's a good leadership trait to have, because that empowers people to be the best version of themselves, and do their best work, and make them feel good, and it makes you personable, and it makes you somebody people want to be around. So if that's your personality, if that's what you're about, I don't think you're going to have trouble in adulthood, having people remember you 10 years down the line. What is going to be a problem is that you don't have anybody that you're close with outside your grandmother right now, and she's not going to be around forever. I hate to say, you're going to have to have some other people in your life that you're starting these long lifetime relationships with.
LAKEISHA: That's what terrifies me, though, because I had that, or I thought I did. years of sitting at the same lunch table, four years of volleyball practice, sleepovers, all of it. And tonight, it was like none of it ever happened.
LUKE: I talked to one person from high school. I think most people don't talk to people from high school anymore. Especially if you've ever moved or changed locations, it makes sense to not stay tight with the people that, you know, you were tight with when you were 15. But you're not 15 anymore. And you had a... a wake-up call tonight or whenever you had your reunion. And it's time to move forward with your life and do something for you. You took care of your mother when she was sick, and that was a great thing. You spent time with your grandmother and keeping her company, and that's a great thing. But you also have to take care of yourself and your own social, not your social standing, but your social health if being around people is important to you. Now, you've got... important skills that not everybody has, especially when it comes to fundraising. So my recommendation is to get involved with maybe a town board or a committee, a chamber of commerce, and get yourself plugged into the community and start making some friends and doing stuff with them. The casino is probably not the place for that, but there are lots of places around where you can find your fitting and provide a lot to... to those places.
AKEESHAH: You're right. I know you're right. It's just... I've been in this holding pattern for so long, waiting for life to start happening again after my mom died. And tonight made me realize I've been invisible this whole time, not just at the reunion, at work, in town, everywhere.
LUKE: Well, if it's important to you to be seen, then go be seen. And there's lots of ways to do that, and you have the skills. So make it happen. If it's not important to be seen, you don't have to. It doesn't matter either way. It's up to you. What's important to you in your life? And it sounds like right now you're feeling like it's important to be seen. So go get yourself seen.
AKEESHAH: I don't even know where to start. Like, do I just walk into the Chamber of Commerce and say, hey, I used to organize high school banquets. Hire me? That sounds ridiculous.
LUKE: Well, you don't know. You don't have to work for them. But, yeah, if you walk into pretty much any place in the country and say I'm good at raising funds, then, yeah, they're going to want to work with you.
AKEESHAH: Huh. I never thought about it like that. I've just been thinking of it as this high school thing I used to do. Not like an actual skill people need.
LUKE: Well, it is a very important skill that a lot of people need. You could join a political party and do it for a candidate that you agree with their platform. for the betterment of society, for social reasons. You could fundraise for yourself. Everybody needs a fundraiser, and if you enjoy that work and you're good at it, you're not going to struggle for work.
AKEESHAH: You know what's funny? I actually liked it. The organizing part. Getting people to show up. Making them feel like they mattered. At the casino, I'm just...
LUKE: Well, at the casino, you're raising funds for the... the casino, which is fine. I mean, I like to go to the casino, and there are dealers there at all the games that make it fun and inviting, and they're very pleasant to be around and memorable. So even if you stayed at the casino, you can still utilize those skills and make yourself seen. But I would recommend, since you've described what you enjoy doing, that you go do that.
AKEESHAH: Yeah, yeah. I could actually do that. There's a literacy thing my grandmother mentioned. They're trying to get funding for after-school tutoring. I could call them Monday.
LUKE: Well, there you go. Call them Monday and get yourself started, and when you plug yourself into the community like that, you're going to make friends, and they're going to remember you, especially if you've raised them half a million dollars. You're not going to be forgotten. But don't worry about high school. Everybody forgets high school. It's a forgettable time.
AKEESHAH: You're right. God, I've been holding on to that so tight, like those four years were supposed to define everything, and instead they just evaporated. But maybe that's okay.
LUKE: That's okay, and I think most people are glad that those years evaporated, because they're not the ones that define us. They're the ones that kind of shape how our adulthood begins, but they're plastic.
AKEESHAH: Yeah, that's a good way to put it. Like, it's not set in stone. I can still...
LUKE: You can still do whatever it is you want to do. So I recommend that you go do that. And we're running out of time, so I've got to hang up with you, but I appreciate the call. And I hope you feel better and move forward with your life. Don't worry about being recognized by people you don't even talk to anymore.
AKEESHAH: Thank you, Luke. I really... Yeah, I'm going to make that call Monday.
LUKE: All right. Well, thank you for the call, Akeeshah. I hope that it works out. And you find yourself in a new life that brings you great abundance and joy. And that's the end of our show.
@@ -0,0 +1,387 @@
LUKE: All right, welcome back. I'm your host Luke. This is Luke at the Roost, the call-in radio show where you can call in and tell me about what's going on in your life. I'll give you the best advice I can. If you'd like to call in, the number is 208-439-58-3. That's 208-439 Luke. If you're not near a phone or you can't call right now, we can take emails. You can email us at submissions at Luke at the Roast. We'll take your email and if I think it's funny enough, we might read it on the show. So, with that being said, it's Sunday, February 22nd and we're going to get to our callers here. The phone is lighting up and today we've got Dexter. Dexter, thanks for calling in. What's going on in your life tonight?
DEXTER: Hey, Luke. Yeah. So I've been reading this Marcus Aurelius book. Right. The Stoic philosophy stuff. And I actually think it's making me worse. Like, I'm getting good at not feeling anything and my mom called me out on it tonight and now I'm sitting here wondering if I've just traded one kind of numbness for another.
LUKE: Are you talking about meditations? I think maybe you're missing the point. Marcus Aurelius never said not to have any emotions. He said not to let them control you. There's a difference.
DEXTER: Yeah, meditations. That's the one. And okay, maybe you're right. But here's the thing. This afternoon, my boss just tore into this kid for 20 minutes straight. I mean really laying into him over something that wasn't even that big of a deal, and I just stood there watching it happen. Didn't feel angry, didn't feel bad for the kid, didn't feel anything. I was just breathing through it like the book says, staying calm, and afterward I felt nothing, which I thought was the whole point, but then my mom calls, and she can tell something's off just from my voice.
LUKE: Yeah, well, it doesn't sound like you felt nothing, made an impression on you. You're still thinking about it now. So it obviously made you feel something that maybe you're trying to repress or push away or not acknowledge. But it did something to you to keep it on your mind for this long.
DEXTER: Shit. Yeah, okay, you're right. I'm still thinking about it because it pissed me off. Not even at my boss, really. I mean, he's always been like that, but at myself for just standing there.
LUKE: Well, yep, that is a little bit, if you think it was wrong and you didn't stand up for yourself and for the kid, then maybe that's a little bit cowardly. And are you going to do the same thing next time? Or are you going to, I mean, Marcus Aurelius would tell you to learn from your mistakes and not make the same mistake twice.
DEXTER: You're not wrong. It was cowardly. The kid's name is Jamie. He's 19, been here maybe three months, and he left a gate open that let some cattle drift.
LUKE: Well, you know, you might not know the whole story. You might have done that three or four times and been told nicely not to do it and continue to do it anyway. So maybe that kid Jamie deserves the strong wording.
DEXTER: No, that's fair. I don't know the whole history there. But it wasn't even really about the gate. My boss was going off about how Janie's generation doesn't respect anything. Doesn't know how to work. All this other stuff that had nothing to do with cattle. It turned into this whole thing about kids these days and their phones and whatever. that's the part that got under my skin. Watching this kid just take it with his head down while my boss used him as a punching bag for everything else he's mad about in the world.
LUKE: Well, what's any of that got to do with you?
DEXTER: Because I've been that kid. Eight years ago when I started here, I was Jamie, screwing up, getting yelled at. Nobody's stepping in. And I told myself back then that if I ever got to where I am now, where the younger guys look up to me, stand there and watch it happen to someone else. But that's exactly what I did today. I broke a promise to myself, and I'm trying to use this philosophy book to convince myself it was the smart move to stay quiet.
LUKE: Well, it sounds like you know whether it was the smart move or not, but just to play devil's advocate here, Jamie is 19 years old, he's an adult, and he's in the real world and a real job, and he has to learn to stand up for himself. I think we all do. So I mean, we've all had that boss that is overbearing and unreasonable, and flat out mean. And at some point, part of growth is learning how to stand up to that and to say, hey, I'm not going to let you speak to me that way. If you've got a problem with the gate, go close the fucking gate yourself.
DEXTER: You're absolutely right. And that's exactly what I've been telling myself all night. He's got to learn to handle it himself. It's not my job to fight his battles. But here's what I keep coming back to. When I was 19 and getting treated like that, I didn't have anybody show me it was okay to push back. I just learned to take it and shut down. Which is why eight years later, I'm real good at feeling nothing when I should feel something. So, yeah, maybe Jamie needs to learn to stand up for himself. But how's he supposed to learn that when everyone around him is mommeling the opposite?
LUKE: Well, it sounds like you're lying to yourself. You're not feeling nothing. You're feeling cowardly. You're feeling shame in yourself for not standing up when you saw an injustice in the world. And the stoic way to handle that would be to acknowledge it and make sure that you don't do that again in the future without dwelling on it or feeling remorse or guilt unnecessarily for a long time and wasting time that you could be being productive. So if I were you, I would take both of them aside the next time you had the opportunity and explain to the kid that he has to stand up for himself and explain to the boss that what you saw was unaccepted. That is leadership and that is the Stoic philosophy. So keep reading the book and do your best and don't beat yourself up, but move forward in a productive way.
DEXTER: Yeah, you're right. I am feeling it. I've been sitting here stewing for hours pretending I'm above it. And you just laid out exactly what I need to do, which is the scary part, because now I actually have to do it. Talk to both of them.
LUKE: That's it. Just talk to both of them. And the more you do that, the more confidence you'll have in doing that and the less scary it'll become in the future. Because this isn't something that is going to be the end of it, right? You're going to have to deal with this all throughout your life. You're going to have to stand up for yourself and for others and show leadership when there is injustice in the world. If that's who you want to be and it sounds like it is. So don't worry about it. Get some sleep. And tomorrow have those conversations and be confident and say your peace and accept the result and do what you can. But you can't change, what you can't change, you can make an attempt. And that's all you can do.
DEXTER: You know what, Luke? I needed to hear that. I really did. I've been sitting here with this book trying to find permission to do nothing. And you just called me out on it.
LUKE: Well, you're obviously not reading the book then, because it says exactly what I'm telling you. It's not a rare book. A lot of people have read meditations. And, uh, the Stoic philosophy is pretty well understood. So if what you're getting out of that book is to stay quiet and feel nothing, then we're reading different books.
DEXTER: No, you're right. I'm cherry-picking the parts that let me off the hook. The whole, except what you can't control thing. I've been using it backwards. Using it as an excuse not to try instead of his piece after I've actually done something. That's not Stoicism. That's just being a coward with a fancy excuse. Appreciate you straightening me out, Luke.
LUKE: No problem. That's what we do here at Luke and the Roost is we straighten people out. Next up on the show, we have Candy. Candy, welcome to the show.
CARL: Do you remember that show Strangers with Candy? It was this lady that was all drunk and crazy on Comedy Central. Oh, man. Yeah. Amy Sedaris, right? My sister used to watch that show all the time. I never really got into it, though. I was more of a Reno 911 person back then. But hey, Luke. I got to tell you why I'm actually calling. Because I'm sitting in my bathroom with the shower running so my kid doesn't hear me, and I'm kind of losing it here. My 14-year-old just told me she wants to move to Tucson to live with her dad and his new wife. And I think the worst part is she had this whole speech prepared like she was breaking up with me.
LUKE: You know what I always say? 911 was an inside job. So about the 14-year-old, I think that's very normal. I did that myself. I think a lot of. lot of children of divorced parents think the grass is going to be greener on the other side or just want to try to live a different lifestyle, a different life. It's a pretty normal thing. So that might hurt, but she'll probably be back when she realizes dad's house isn't as fun as she thought it was going to be.
CARL: Yeah, I mean, you're probably right. But here's the thing. I've been running my mom's bakery since I was 21, getting up at 3 in the morning single day and this girl has never wanted for anything her dad moved to Tucson three years ago with this woman who works from home doing something with marketing or whatever and now suddenly that looks better than what I'm doing like I get it the new wife doesn't smell like yeast and actually has time to do her nails but I've been busting my ass here and the way she said it Luke she wouldn't even look at me
LUKE: yeah but it's not really about you and it doesn't matter what sort of life you gave her She would still be curious and think that it was better somewhere else. That's just human nature. So don't take it personally. It's not personal. Unless you guys have some other darkness in the relationship I'm not aware of, my guess is just that it looks like more fun over there, and she wants to go see what that's like.
CARL: You're right. You're right. I mean, there's no darkness or anything like that. We get along fine, or at least I thought we did.
LUKE: Well, Take advantage of the situation. Let her do it. Let her dad take care of the kid and then that frees you up to do some stuff that maybe you wouldn't otherwise be able to do. Enjoy it while it lasts and be there for her if she wants to come back.
CARL: Okay, but see, that's where my head goes to a dark place. Because part of me is actually relieved, and that makes me feel like the worst mother in the world. Like, I could sleep past three in the morning. I could maybe go on an actual date without worrying about getting home. I've been This alone for so long that when she said it, there was this split second where I thought, oh, thank God, before the hurt even kicked in.
LUKE: Yeah, and there's nothing wrong with that. I mean, you're a person too, and it would be nice for you to be able to live some of your youthful years on your own. So if that's what everybody wants, if everybody's in agreement, the kid wants to go there, the dad is okay with it, and you're okay with it. It sounds like a good thing and a change for everybody. So he gets to spend more time with his daughter. You get to spend more time with yourself, and she gets to see what it's like living in other conditions. Everybody wins.
CARL: You know what? When you say it like that, it doesn't sound so pathetic. I think I've just been so stuck in this loop of work and being mom that I forgot I'm actually only 23. What? My sister Brenda keeps telling me I need to live a little, but we're not even talking right now because of some stupid fight about passwords and privacy stuff. God, I sound like a mess, don't I? But okay, yeah, maybe this is actually a good thing.
LUKE: So you're 23 years old and you have a 14-year-old daughter?
CARL: Yeah, I had her when I was nine. No, I'm sorry. God, I'm all over the place tonight. I'm 37, not 23. The bakery, I've been running it for two years since my mom had her stroke. And I'm 37.
LUKE: Okay, well, you're 37, and now you have the opportunity to be single at 37 some of your life while you're still young. I think it's great. Let her do it. Be supportive. Maybe see her on the weekends.
CARL: Very good. You're right. Yeah, you're totally right. I'm going to tell her tomorrow that if this is what she wants, we'll figure it out. Maybe I'll even get one of those dating apps everyone's always talking about. Though honestly, the thought of that terrifies me more than telling my daughter she can go.
LUKE: Tell her she can go. And, yeah, I mean, I think those dating apps are probably less fun and you're imagining that they are, but give it a shot. Especially if you haven't yet, then yeah, that's exciting. And nobody's getting hurt here. So great. It's a win for everybody. Do it and enjoy your life.
CARL: Okay, yeah. Thank you, Luke. I really needed to hear that. I've been sitting in here with the shower running so she wouldn't hear me lose it. And now I feel like maybe I'm not completely screwing this up. I'm going to go talk to her tomorrow and actually be supportive instead of making it about me.
LUKE: That sounds like a good plan, Candy. You're a good mom, and you deserve some happiness in your life. So go get it. And when dad's house sucks and she wants to come home, be ready for that. That's all. And now, ladies and gentlemen, we have to take a word from our sponsor, Squarehole. I'm going to get dark for a second. You're going to die someday. Not today, probably. But someday, and when you do, what's going to represent your business? A Craigslist post from 2020? A Google Maps pin that still says permanently closed because you forgot to update it after COVID. A nephew who knows computers but actually just has a Reddit account? Your legacy deserves better than a nephew with a Reddit account. Squarehole lets you build something that outlasts you. Something your kids can point to and say, Dad built that. They won't know it took you a lot. minutes, that's between you and Squarehole. And when your nephew says, I could have built that for you for free, you can look them in the eye and say, then why didn't you, Tyler? I asked you in 2021 and you said you were busy and you were not busy. Squarehole. Build it before Tyler has to, because Tyler is not going to. Okay, thanks to Squarehole for sponsoring this episode. Next up on the line we have Carl. Carl, welcome to the show. What's going on out there for you tonight?
CARL: Luke, hey, so I just yelled at my daughter about her car registration, and I swear to God it was my dad's voice coming out of my mouth. Like word for word what he used to say to me. You think I'm made of money? You think this shit just appears. I heard myself say it, and I just froze.
LUKE: Okay, what happened? What's the situation with the registration, and why did it cost you money?
CARL: So she's got this beat up. up Civic, right? Registrations like 200 bucks, and she asked if I could help cover it because she's been picking up extra shifts at Target, but they cut her hours, which is totally reasonable. But I just got hit with this storage facility repair bill. Some kid broke into a unit last week, busted the lock mechanism on the whole row.
LUKE: Okay, don't you have insurance on your business?
CARL: Yeah, but there's a deductible, and Ray, he's the guy I work with on weekends. He's telling me we should upgrade the whole security system while we're at it because, This is the third break-in in six months. So I'm looking at maybe 1,500 out of pocket if I do it right. But that's not even the point. The point is, I had the $200.
LUKE: Yeah, and you reacted in a negative way to your kid when she asked for the money because you're under stress, and that's a normal human thing. People say things they don't mean and react too strongly. So. So what are you going to do? Apologize and move on?
CARL: I mean, yeah. I called her back like 20 minutes later and apologized. She was cool about it.
CARL: She's a good kid. But Luke, that's not what's messing me up.
LUKE: What is messing you up?
CARL: That you sounded like your dad? Everybody sounds like their dad. No, I know that. I get that. But it's like, okay, my old man died four years ago and we barely spoke the last decade of his life because of exactly this kind of thing. He'd blow up over money, over nothing, make you feel like asking for help was this huge burden.
LUKE: Well, sometimes it can be a huge burden, but it sounds like in this case it wasn't a huge burden to you. You made a mistake, you apologized for it, and what are you going to do in the future to make sure that doesn't happen again? Like, if you don't want to be your dad or act like your dad, then you have to take steps to avoid that. And what are those steps going to be?
CARL: That's the thing. I've been taking steps for years. I go to this therapist every other week. I read all this stuff about breaking soul cycles. I've been so careful about not doing what he did. And then one bad moment, and it all just comes right back out like it was sitting there waiting.
LUKE: Yeah, well, it happens. Everybody makes a mistake once in a while and says things that they don't mean and later regret. I do it all the time, and I always have to constantly try and stop myself when I can feel the hairs standing up on the back of my neck, and I'm about to snap at somebody. And sometimes I do better than others, that's the human experience.
CARL: You're right. I know you're right. It's just, I heard Dexter earlier, you know, you know. And I felt like he was doing the same thing I do, using all this self-improvement stuff as like, I don't know, a shield or something.
LUKE: Yeah, but all this self-improvement stuff, none of it expects you to obtain this level of perfection. Right? Every single self-help book or philosophy centers around continuous improvement, an acknowledgement of when you're wrong. And it sounds like you did those things. You acknowledged that you were wrong. You called, you made a mistake. She forgave you. She knows that you weren't trying to hurt her and that you were under stress at work. So this doesn't sound like it's a thing. Just do your best to make sure this doesn't continue to happen and try to be a good person. That's all you can do.
CARL: Yeah. Yeah, you're right. I guess I'm making this bigger than it needs to be. I just got scared for a second that all the work I've been doing doesn't actually matter, you know? Like it's all still in there no matter what I do.
LUKE: Yeah, well, it is always going to be in there no matter what you do. It's not like it's ever gone. I mean, you might be able to go sit with some monks on a mountain in Tibet and meditate it away, but I suspect that it's still in there somewhere with the potential to come out. So you just keep doing what you're doing. Keep working with your therapist. Keep acknowledging when you're wrong and do the best you can to correct those instances when they arise.
CARL: Fair. I appreciate it, Luke. I think I just needed to hear somebody say it's not the end of the world, you know. I was sitting here in this office just spiraling about it.
LUKE: It is not the end of the world, Carl. You're doing decent. You're doing pretty good. So keep doing what you're doing and things will get better. Next up we have Monique. Monique, welcome to the show. What's going on with you tonight?
MONIQUE: Hey, Luke, thanks for taking my call. So I said yes to my boyfriend's proposal three days ago in front of both our families. And I'm pretty sure I don't actually want to marry him.
LUKE: Okay, well, luckily for you, you haven't signed any papers yet, so you don't have to marry him.
MONIQUE: Right, no. I know that. It's just he did it at my aunt's house during Thanksgiving dessert with like 20 people watching and everyone's already texting me about venues and my mom's crying happy tears every time she sees me and I keep looking at this ring on my finger at work, thinking, when did I stop wanting this? Was it before he asked or the second he got down on one knee in front of everybody?
LUKE: Well, it could just be jitters. Maybe you did want that, and now that it's happened, it feels real to you. Or maybe you really don't want to get married. But the advice I'm going to give you is you can't make that decision based on what everybody else is feeling, because you're the one that is getting married. It's your life. And a little bit of embarrassment over changing your mind is much safer and less harmful than a lifetime marriage that you don't want to be in.
MONIQUE: Yeah. No, you're right. I know you're right. It's just he's a good guy, you know? Like, objectively, he's nice, and he has a job and he doesn't yell at me or anything. My mom keeps saying, I'd be crazy to let him go.
LUKE: Well, what your mom says really doesn't matter, and that's a pretty low bar. He's nice, and he has a job, and he doesn't yell at you. I don't know that that's enough qualification for a lifetime commitment to one person. So I think if you're having second thoughts, there's probably a good reason for that. You're not ready for marriage. And maybe you should just tell him, hey, I reacted too quickly. This isn't something I want right now. Maybe we can revisit it in the future.
MONIQUE: That's the thing, though. I don't think I want to revisit it in the future. Like we've been together four years. And I keep waiting to feel that thing everyone talks about. That likes certainty. And it's just not there. I watch him play video games every night. And I think, is this it? Is this what I want for the next 50 years and then I feel like a bitch because he's perfectly fine. There's nothing wrong with him.
LUKE: You don't have to feel like a bitch and you don't have to want to get married and you don't have to do what your mom says and you don't have to do what he wants. All you have to do is make the best decisions you can for your own life and what you want. And right now it sounds like that's not marriage to this guy. And maybe you don't want to be with him at all and that's okay too. But it sounds like maybe here and make sure that he knows that because that's the respectful thing to do. If you said yes and he's all excited and he thinks he's getting married but you're not into it, you need to let him know. And that's going to be a hard and awkward conversation, but it's the right thing to do and you should do that sooner rather than later.
MONIQUE: You're right. God. You're right. I heard Candy earlier talking about feeling relieved about her daughter leaving, and I felt so called out because that's exactly how I feel. Like, everyone's congratulating me and I just keep thinking about how I could just keep working my shifts and go home to my own apartment and not have to pretend I care about his fantasy football league.
LUKE: Yep. If that's what you're happy with in your life right now, then that's fine. And it's acceptable and you don't have to feel guilty or wrong about that. So tell the guy you don't want to get married and reassess whether you want to be in a relationship at all. You don't have to be. And then just move forward after that.
MONIQUE: Okay. Okay. I'm going to do it. I'm going to tell him tomorrow after my shift. I've got like 20 minutes left on my break, and then I got to go back and serve hash browns. But yeah, tomorrow.
LUKE: Okay, good luck with that conversation. I hope it all works out for you. But yeah, definitely don't get married if you're not even sure you want to continue being his girlfriend. That would be insincere and put you in a bad position later in life. Next up, we've got Cliff. Cliff, welcome to the show. What would you like to talk about tonight, sir?
CLIFF: Hey, Luke. Yeah. Thanks for taking my call. So I found $47,000 in credit card debt hidden behind my ex-wife's dresser this afternoon, all in both our names, and she's been gone two years.
LUKE: That sounds odd to me.
LUKE: There's 47 large in debt, and you don't know what it's for, and you didn't know anything about it, and in two years the collections agency never came looking for you. That doesn't sound right.
CALLER: No, you're right. That does sound weird. So what happened is the statements are rolled from like 22, right before she left. I've been getting collection calls, but I thought it was just the normal stuff we had together when we split. Like maybe eight grand I've been chipping away at. I didn't know about this debt because she was handling all the bills back then. I work outside all day. She worked from home doing medical billing, so she just took care of that stuff.
LUKE: Well, if it's $47,000 today, it was probably half that two years ago, and this is compounding fees for nonpayment. I would venture to guess that you can have a lot of that forgiven if you work with a debt consolidation company, but it looks like it's, if it's valid debt, I mean, that's money that you spent some years ago with your wife. That's how it works. You're going to be on the hook for a portion of that at least.
CALLER: Yeah. No, I get that. The thing is, though, I didn't spend it. Like, I'm looking at these statements right now and it's stuff I never saw.
LUKE: Well, if you were married to the woman and your name is on the account and there were charges made on that account, then, yeah, you did spend it.
CALLER: Okay, but Luke, I'm saying I literally didn't know these cards existed. Like there's a capital one here. A discover. Two different chase cards I knew about our one joint card were used for groceries and gas. These other ones, she must have opened them and just put my name on them without telling me. I never saw the bills. I never used the cards. I didn't even know they were being opened.
LUKE: Well, I don't know. I'm not a lawyer, as I've said before. I don't know that you can reopen a divorce case like that. I'm sure a settlement was already made and all the money was already split. There was a discovery and everybody had to disclose all of the financial records. So if this was not part of that discovery, you might be able to go back to the court and say, hey, there was a fraud here and have some recourse that way. But I don't know. I would recommend that you talk to a lawyer and find out if that is valid and potentially sue her.
CALLER: Yeah. I mean, we didn't actually go through the courts. We just split up. She moved to lost cruises with some guy she met online, and we divided things ourselves. I kept the truck. She took her car. We both just walked away. We were only married three years, didn't have kids, didn't own anything together except the debt I knew about. So there was no official settlement or discovery or any of that.
LUKE: Well, then you have no recourse at all, sir, and you made a horrible mistake. And now you are going to have to deal with that and maybe talk to one of those debt consolidation companies and see how much of that debt can be forgiven.
CALLER: Jesus. Okay. So I'm just screwed then. I've been trying to buy a house, been saving up, and now I find out I'm underwater by 50 grand because she was what buying stuff online for six months before she bailed. And I just have to eat it.
LUKE: Well, you have to do more than eat it. I mean, you're not going to buy a house. If you haven't paid the bill in two years on this credit card, then your credit is destroyed. But if your name's on the account card, then that's your debt.
CALLER: Yeah, my credit's already trashed. I know that now. I've been getting rejected for loans and I couldn't figure out why because I thought I was doing everything right, paying my bills, running my business clean, staying out of trouble. And the whole time it's this. What kills me is she knew. And that's why I tell everybody don't get married. It doesn't matter how much love you're in. Just don't do it because you don't know another person. You can't. And they change and they do shady shit.
LUKE: So My recommendation to literally everybody is, don't get married. You did. You got burned. And then you split it up without going to court and having discoveries made. And now you're in trouble. So, I mean, your credit's trashed. Her credit's also trashed. And she's got the debt too. So she's not buying a house either. Your best course of action, I believe, is to talk to a debt consolidation company.
CALLER: Yeah, you're probably right. I just keep thinking about all those months before she left, how she was acting weird, distant, and I thought maybe she was just stressed about work or something. Now I'm realizing she was probably panicking because she'd racked up all this debt and didn't know how to tell me or didn't care to tell me. I don't even know which is worse.
LUKE: It doesn't really matter what she told you or how she felt. What matters is you have this debt now and you could have avoided that by being responsible in checking your credit periodically to know what it was being used for, but you didn't. And you didn't follow any legal process to be divorced. And now you're where you're at. And it's a hard lesson to learn, but you're going to have to learn it.
CALLER: You're right. I should have been checking. I just trusted her, which was stupid. I mean, we were married. I thought that meant something.
LUKE: You thought wrong, sir. No, that is incorrect. That means absolutely nothing. So, um, sorry that happened to you. And hopefully you can make a payment plan or get it settled some other way.
CALLER: Yeah. All right. Well, thanks for the reality check, I guess. I'll call one of those debt companies tomorrow and see what they can do.
LUKE: Okay. Ladies and gentlemen, this is a public service announcement. Do not get married and periodically check your credit because somebody could be screwing with you. And it may or may not be somebody that you even know. So now we have to take a little break in here from one of our sponsors, okay? Here we go.
Need to pawn a TV, but also get your kids with you? We get it. Life's complicated. That's why Big Terrence opened Lil Rascal's pawn and daycare. The only business in the tri-state area where you can hawk a generator and get two hours of supervised child enrichment. All under one roof. Here's the layout. Left side of the building, pawnchop. guitars, power tools, a surprising number of saxophones. Right side of the building, a full side of the building, a full functioning daycare with coloring books, juice boxes, and Miss Patty, who's been watching children since 1987 and has never once raised her voice because Miss Patty doesn't need to. Miss Patty communicates through eye contact and an energy that can only be described as corrective. Is there a wall between the two sides? There is now. The inspector was very clear about that. Big Terrence installed drywall the same week. Is it load bearing? Big Terrance says don't worry about it. off your kid. Pond your stuff. Pick up both on the way home. Little Rascals pawn and daycare. Two businesses. One building. Zero complaints that have held up in court. Open six days a week. Close Sunday because Miss Patty goes to church and Big Terrence respects that.
Okay, and we are back. Let's see. We've got a returning caller tonight. Hey, Rochelle, welcome back to the show. What's happening with you tonight.
ROCHELLE: Hey, Luke. So I did it. I turned off location sharing. Like, officially, pulled the trigger this afternoon. And here's the thing. I've been sitting out here for like three hours, just...
LUKE: All right, so you're not sharing your location. Nobody knows where you're sitting. Why are you sitting there? And how is this relevant?
ROCHELLE: Because he texted me two hours ago asking if I was okay. Like, hey, you good? Haven't seen you move all day. Which means he was checking. Still checking.
LUKE: All right. So now you're off the grid. Congratulations. What are you going to do now?
ROCHELLE: I mean, honestly, I don't know. That's why I'm calling. Because I didn't text him back.
LUKE: Next on the show we have Frank. Frank, Frank, welcome to the show. What's going on out there in your life?
FRANK: Hey, Luke. Thanks for taking my call. So I just watched my boss present my entire project to the board this afternoon. Like word for word, my proposal, my numbers, even my stupid joke about sunshine and got a standing ovation while I sat in the back holding the printouts I made.
LUKE: Okay, and obviously you don't feel good about that. This is your boss. That's what bosses do.
FRANK: I mean, yeah, I get that bosses take credit. But this was different. This wasn't like he polished up a team effort or whatever. I spent three weeks on this cost-saving analysis, stayed late-running numbers, and he literally presented my slides with his name on them. He even practiced my delivery, Luke.
LUKE: That's dirty, dirty, dirty, but, uh, you know, You got paid anyway, right?
FRANK: Yeah. I got paid my same $42,000 a year to install solar panels and apparently write his presentations for him. Meanwhile, he's probably getting a bonus for my work. And the thing is, I'm trying to figure out if this is material, or if this is just me being bitter, you know? Like I do stand up on the side, and I've been workshopping this whole bit about middle management being plagiarism with a 401k, and now it actually happened, and I don't know if I'm mad or if I just got handed the perfect punchline.
LUKE: Well, both things can be true, because that is absolutely accurate. Middle management is plagiarism with a 401k. That's, like, literally the job description.
FRANK: Right? And the worst part is, I saw it coming. Like, two weeks ago, he asked to review my progress, and I walked him through everything, and there was this moment where I could feel it happening. But I just handed it over, because what else are you going to do? Say no to your
LUKE: You know, I say no to my boss literally all the time. That's what leadership is. So if you want to take control of your career and your own work and goals, and then yeah, you've got to say no sometimes, even to your boss. That's what they, when they talk about speaking truth to power, sometimes you have to actually do that.
FRANK: Okay, but you're Luke at the roost, man. You literally are your own boss. I'm 21 years old installing solar panels in Tucson. If I tell my boss, know, I'm just unemployed with two ex-wives in a Tuesday night slot at a bar that seats 40 people.
LUKE: Okay, well, there's a lot to unpack there. Uh, you're two ex-wives at 21 years old? What the, what the fuck are you doing?
FRANK: Yeah, I know how it sounds. First one was right out of high school. Lasted eight months. Second one, I met at an open mic. Thought we had this whole creative connection thing. That was done in a year. My mom says I'm practicing, but I think I'm just really bad at knowing when to walk away from which is probably why I'm still at this job watching my boss steal my work.
LUKE: Dude, you're 21 years old. You haven't had enough time to walk away from anything. So maybe it is time for a new job, but I think that you should just eat this one, because this is how the workplace works. You work for a guy, you get him the data, he presents the data, you're not in a position to be presenting data to a board, so if you're not okay with that, that's all right. But it sounds like that like in your position, that's what's expected and that's what you're paid for. So do your work and give it to your boss and let him, you know, look good. It's your job to prop him up.
FRANK: See, that's what I keep telling myself, but it doesn't sit right, because it wasn't like he asked me to pull some numbers for him. He asked me to analyze our installation efficiency across six sites and propose cost reductions.
LUKE: Great, and that was a wonderful opportunity for you to learn the of research and writing those papers. And you saw that it went well. So you're learning valuable experience in the workplace. You're being compensated for it. And you're 21 years old. So I wouldn't worry too much about any of this. Just learn what you can about the business and about business in general. And when you're ready, then move on. And you'll be able to write presentations when you are addressing the board as the supervisor or the manager or the owner or whatever it is.
FRANK: Yeah, but that's the thing, though. I'm not trying to be a manager at a solar panel company. I'm trying to do comedy. This job is supposed to be the thing that pays my rent while I work on my actual career.
LUKE: All right, then. There's no problem here. Who cares who presented the work to the, what do you expect? What do you want a cookie? You're not going to get a bonus. You're at the, you're the low man on the totem pole, and that's the way it works.
LUKE: So if it's just your job, do your job, and then on the side, do your comedy until that takes off. And if it doesn't take off, well then you've got more valuable experience for your actual career.
EARL: Okay, but I had a bit about this. Like a whole thing I'd been working on about how my boss operates, and now I watched him do the exact thing I was going to talk about on stage. Except it happened to me, and I just sat there. That's not material anymore. That's just humiliating. How am I supposed to get up there Tuesday and tell that story when the punchline is me being too chicken shit to say anything?
LUKE: Because that's what makes it real. That's what makes it good material. it actually happened to you. It's real and people can identify with it. So, yeah, workshop that material. And now you have a different place for it to come from. It's not something that you just made up as a joke. It's a joke based in the reality of your life, which makes you a better comic.
EARL: Huh. I didn't think about it that way. So you're saying the fact that I just sat there like an idiot actually makes it better?
LUKE: No, not really. I'm saying the fact that you're the self-deprecating and honesty of the joke is what makes it better. It makes it more real and relatable because everybody's been in that situation and can understand it a little bit better. And when you present that joke, since it's coming from a real place, it's going to hit with that realism.
EARL: Yeah, okay. That actually helps. Because I've been sitting here thinking, I need to quit or confront him or something, but really, I just need to tell the truth about it on stage.
LUKE: If the solar panel business, and business management is what you intend to do for your career, then yeah, maybe you need to quit. But if this is just your job while you're working on the thing that you love, then who cares? Do your job, do it as well as you can, and move on. It doesn't matter if you get to address the board or not, because theoretically, you're not going to see them again in a couple of years anyway.
EARL: You're right. I've been making this into some big thing when it's just a paycheck. The real work happens Tuesday night at the open mic, not Monday morning. some conference room.
LUKE: Well, there you go, but only one of them brings you a paycheck, so make sure you keep doing that one.
EARL: Yeah, yeah, I hear you. Show up, install the panels, cash the check, write the jokes. I can do that. Thanks, Luke.
LUKE: Yeah, no problem. My other bit of advice is don't get fucking married again. What are you doing? Jesus Christ, with you people and your marriages, stop it. Just stop it. Amber, welcome to the show. Don't get married, okay?
AMBER: Oh, God, I'm not getting married. I'm calling because I just blew up my entire life with one stupid text message, and I don't know how to fix it.
LUKE: All right, what's the text message?
AMBER: Okay, so there's this guy Derek at work. He does vehicle registration, and we've been flirting for like two months. Nothing physical, just stupid jokes about physics, because I work in the DMB records department, and he thinks it's funny when I made nerdy references. So tonight, I'm sitting here having some. and I sent him this text about collapsing wave functions, like a flirty science thing, except I sent it to his wife Michelle instead. His wife Michelle, who I have never met, but whose number is in my phone, because she called me once about Derek's schedule.
LUKE: All right, well, it sounds a bit dramatic. If you send a text message about collapsing wave functions to a random woman, well, wait a second, what? Why do you have her number? Wait, she called you once about Derek's schedule, so her number is in your phone. You saved it? I don't understand how this could have happened. But anyway, regardless, irregardless. Regardless, irregardless. I don't know. Whichever. Whatever. Here's the deal. That's not sexy. And she's just going to see that and think it's spam.
AMBER: No, no, no. You don't understand. The text said, hey, been thinking about our conversation yesterday. Pretty sure if we spent enough time together, our wave functions would definitely collapse with a winky face. Luke. That is not spam. That is clearly me hitting on her husband.
LUKE: Okay. Well, I mean, take some solace in the fact that she probably didn't save your number because she's probably not crazy. And as far as she knows, that's a text message from a stranger to the wrong number.
AMBER: Except I signed it. I wrote A at the end. And we all work in the same building. She knows who I am because Derek talks about work at home. He told me that once.
LUKE: All right. Well, who is this woman? her in person before? What she looked like? Can she kick your ass?
AMBER: I've never met her, but I've seen pictures on Derek's desk. She's like this tiny blonde yoga instructor type. I don't know if she can kick my ass, but she could definitely ruin my life at work. Derek and I work in the same county office building.
LUKE: Well, I hate to tell you this, but it sounds like you deserve it because you're trying to break up a home here, and you're messing around with a dude at an office, and everybody knows you don't do that. So you're doing a bunch of stupid shit. And maybe you deserve a smack in the face for it.
AMBER: You're right. You're absolutely right. And the worst part is, I've been lying to myself about it for two months. Like, it was just harmless fun. Like we were just friends who happened to have chemistry. But I knew exactly what I was doing.
LUKE: Okay. Well, uh, is Derek going to leave the wife for you, you think?
AMBER: No. God no. He's never even hinted at that.
LUKE: All right. It sounds like you might be in for an embarrassing, rough week. Or maybe not. never hear another word about it. But if it does come down on you, then take accountability and maybe move on to a new job. And next time, don't do this. Don't ever do it again.
AMBER: You know what's really messing me up, though? I'm sitting here terrified. Michelle's going to blow up my whole life. But part of me is also just devastated that Derek hasn't texted me. Like even now, I'm checking my phone hoping he responds, not her. How pathetic is that?
LUKE: Derek's got a wife. Derek's wife is very upset right now. now at Derek. So Derek's probably dealing with his own home life and not thinking too much about you. And that's what happens when you're the girl on the side at work. You're not important to him in his life. He's married. That's important to him in his life. So you got to, who cares what you feel like? You're the asshole here.
AMBER: Yeah. Yeah, yeah, I am. And I think I knew that the whole time, but I just kept telling myself these little stories about how it was different. How we had this real connection. My mom used to say I overthink. everything, but this is an overthinking when I actually did the stupid thing.
LUKE: Yes, you did. And so did Derek. I mean, Derek's not off the hook here. He's the asshole, too, but I mean, you knew what you're doing, and you did it anyway, and you got caught, and now you have to deal with that.
AMBER: The thing that really kills me is I sent it at like 7.30, it's been three hours. So she's definitely seen it. She's just not responding. And I don't know if that's worse than her responding. You know? Like, is she screenshoting it to send to HR?
LUKE: No. She's fucking destroying her. husband, they're fighting right now, and you're breaking up their marriage. You're driving a rift in between these two married people. That's what's happening right now. She's got better things to worry about than your position. It's not about you.
AMBER: Oh, God. You're right. I've been sitting here making this about me and my embarrassment when I just detonated someone's marriage. Michelle's over there dealing with the fact that her husband's been texting another woman, and I'm worried about whether I'm going to be uncomfortable at work.
LUKE: That is correct. And cut it out. And you're probably going to end up uncomfortable at work, and you deserve that. And maybe it's time to move on.
LUKE: Because, I mean, how's work going to go now that Derek's in trouble and he's not speaking to you? And how are you going to feel either way, actually? He's not going to leave his wife, probably not on purpose anyway. She might leave him. But if he wasn't going to leave her for you, he's still not going to leave her for you. So, I mean, you got to do what you got to do, I guess.
CALLER: Yeah, I need to find a new job. I can't keep working there. Even if nobody finds out officially, I'll know. And every time I see either of them, it's just going to be this horrible reminder of what I did. And you're right. He was never going to leave her. I think I knew that too.
LUKE: Okay, well, you know what you've got to do. You've got to find yourself another job and stop flirting with married men. Unless they've had that conversation and they're open to it. It sounds like that's not the case here. Don't break up people's marriages. It's a good way to get yourself in a situation you don't want to be in. Let's see. Let's see. We're going to take one more call tonight, okay? And we're going to talk to Rick. Rick, welcome to the show. What's going on tonight?
RICK: Oh man, Luke, I got to tell you, I just spent the last three hours reading about quantum physics. And I think I broke my brain. Like, I'm sitting here at my kitchen table. It's almost midnight. And I genuinely cannot figure out if the pass is actually fixed, or if we're somehow changing it right now without knowing it.
LUKE: No, we're not changing it. It is fixed. What happened, happened, whether or not it's still happening in a timeline that we can access is a different question. But if it happened, we can't change it, because if we did, it would open us up to all sorts of paradoxes. And that can't happen. So whatever happened already happened.
RICK: Okay, but see, that's what I thought too, right? That's common sense. But then I'm reading about this delayed choice quantum eraser experiment, and Luke, I swear to God, they're showing that a particle somehow knows what measurement you're going to make on it in the future.
LUKE: And how are they showing that?
RICK: So they shoot a photon through this double slit setup.
LUKE: Right.
RICK: And normally you get an interference pattern. Because the photon goes through both slits at once, like a wave. But here's the thing. They tag the photons so you can tell which slit they went through, which destroys the interference pattern. Except then, after the photon already hit the detector, they erase that which path information.
LUKE: Uh, erase it how? Like, it's recorded on a chart and then the chart is changed somehow?
RICK: No, no. They use these beam splitters and crystals to basically make it so the entangled partner photon either keeps the witch path info or loses it, depending on which detector it hits. And this happens after the first photon already last. But when they sort the data afterwards based on what happened to the second photon, the ones where the info got erased show the interference pattern again. And the ones where it didn't, don't. It's like the photon that already hit the screen somehow knew what was going to happen to its partner later.
LUKE: Well, projecting what happens in the future is not the same as changing what happened in the past.
RICK: Right, but that's what's messing me up. If the photon acts like it went through both slits only when the information gets erased later, doesn't that mean the future measurement is affecting what already happened? Like the photon's already on the detector screen, it already did its thing. But whether it made an interference pattern or not depends on something that hasn't happened yet when it lands.
LUKE: I don't understand this one because if it hit the detector, then it's been detected already. And the next measure, would have to go back and change what happened on the detector, which would be akin to, you know, writing something down and then going back and looking at it and having the letters be in a different order. I don't see how that could happen.
RICK: No, that's the thing. When you look at the detector screen, you just see this blob of dots. It doesn't look like anything meaningful. It's only when you sort those dots afterwards, based on what happened to the entangled partner photon, that you see this blob of dots. You see some of them form an interference pattern and some don't. The pattern was always there in the data. You just couldn't see it until you knew which photons to group together. But here's what's freaking me out. Those photons hit the screen before you made the choice about whether to erase the which path information or not.
LUKE: Well, it sounds to me like it's not changing the data. It's changing the sort of the data, which is happening in real time, right? It's happening in the current time, not the past.
RICK: Yeah, okay, you're right. The sorting is happening now. But Luke, think about what that means. Each individual photon that hit that screen, it somehow already knew which group it was going to be sorted into later. The ones that are going to be in the information erased pile are already showing wave behavior, and the ones in the information kept pile are already showing particle behavior, before anyone decided which measurement to make.
LUKE: Okay. you shoot the photon through the slit and it hits the detector and then you shoot another photon through the slit and it hits the detector and then at some point later you sort that data and it shows a pattern it sounds to me like if anything is being changed it's not what hit the detector it's the sort operation that happens afterwards so i'm failing to understand how it relates to This is not changing the past. It's changing the current.
RICK: No, but. Okay. Let me try this differently. When the photon hits the detector, it's already behaving one way or the other. It either went through both slits like a wave or went through one slit like a particle. That's already determined by the time it lands.
LUKE: Correct. It's determined when it lands based on whether or not it was measured, right? It's the measurement of it that determines whether it's going to be the wave or the particle formation.
RICK: Right. But in the delayed choice version, the measurement that determines whether its wave or particle happens to the photon, the entangled partner, and that happens the first one already landed. That's the delayed choice part. You're deciding whether to measure which path information after the first photon is already on the detector. And somehow, the photon that already landed matches whatever you're about to do to its partner.
LUKE: So what you're about to do? So, Whatever we do to the entangled partner in the present also changes the state of the partner in the past is what this experiment is showing?
RICK: That's what it looks like. Yeah. Or, I mean, that's the part I can't wrap my head around. The experiment shows that the photon that already hit the detector will be grouped with the interference pattern if you erase the witch path info from its partner later and grouped with a particle pattern if you don't. Like its behavior when it's landed was somehow dependent on a choice you haven't made yet.
LUKE: Okay, so how do you erase the which path info?
RICK: You use what they call a quantum eraser, basically another beam splitter that the entangled partner photon goes through. If you send it through that beam splitter, the which path information gets scrambled, erased. You can't tell which slit the original photon went through anymore. But if you don't send it through, if you measure it directly, you preserve that information. And here's the thing. You can make that choice after the first photon already landed.
LUKE: But you cannot make that choice after the first photon was measured, right? So if it was already measured at one state and then they run it through this beam splitter device, the quantum eraser, would it change the result of the previous measurement?
RICK: No. See? That's the thing. You're not re-measuring the first photon. The first photon here.
RICK: hit the detector, that measurement is done. That position is recorded. What you're doing is measuring its entangled partner. Now, if the photon has already hit the detector and been measured, is it still entangled?
LUKE: Yeah, that's okay. That's actually a really good question. Because normally when you measure one entangled particle, the entanglement collapses, right? But in this setup, the first photon hitting the detector isn't a complete measurement of the entangled system. It only tells you where it landed, not which path it took. The which path information is still encoded in the partner photon.
RICK: But that doesn't make sense. I don't see how that can work because if it's collapsed into a non-entangled state after the first measurement, it's they're no longer entangled. So the which path information but it wouldn't still be at the current point, right?
LUKE: You know what? You're right. That's exactly what's messing me up. Because yeah, if measuring one particle collapses the entanglement, then they're not entangled anymore when you get to the second measurement. The Witchpath info would just be sitting there in the partner photon as classical information at that point, not quantum information. But the experiment works.
RICK: Well, there's more that makes this not make sense because If the initial photon was measured at the time of detection, right? It hit the detector. That's the measurement that collapsed the state. The entangled partner, then by definition, would have also already been measured. So you're remeasuring something that's already been measured.
LUKE: Oh shit. Yeah. You're absolutely right. If they're entangled and you measure one, you've measured both. That's the whole point of entanglement.
RICK: Right. So then you can't just re-measure it later and say that it, I don't understand the experiment or what it was trying to prove or how that that results in changing something that's already happened because what happened happened to the first photon and the entangled photon at the same time.
LUKE: Yeah, no. You're okay. So either I'm completely misunderstanding how the delayed choice quantum eraser works or the article I was doing that pop science thing where they make it sound more paradoxical than it actually is. Because you're right, if the measurement collapses the entanglement, then the second measurement is just measuring something that's already determined. There's no retroactive anything. And now, I'm sitting here thinking about all the times I've heard people talk about this experiment like it proves you can change the past. And maybe that's just not what it shows at all.
CALLER: That's changing the future. And that is possible. That is absolutely possible. Yeah, you're right. And we did. I apologized. He apologized. We're playing together again. But I guess I wanted the quantum stuff to mean something bigger than it does.
LUKE: Well, I'm sorry to break your quantum dreams, but as the armchair that I claim to be here. I'm not qualified to make any opinion or theory on any of this, because I understand it about as much as anybody, which is not at all. So take what I say for what it's worth, which is nothing. But the experiment as you described can't work the way that you described it.
CALLER: Well, that makes me feel better. Honestly, I've been sitting here for three hours thinking I was too dumb to understand it. But maybe nobody really understands it. Like, I can read the words wave particle duality and quantum entanglement, but that doesn't mean my brain can actually picture what's happening.
LUKE: Well, I think your brain can picture what's happening. I mean, you could watch a YouTube video and see a motion graphic of what's happening. It's not that hard a concept to grasp the what. The why, however, is nobody understands.
CALLER: Right. Okay, so I can see the double slid experiment animation. I can watch the particle go through both slits until you measure it, and then suddenly it picks one. I can see that happening. But why does observation collapse the wave function? That's the part that makes my head hurt. Like, what is it about measuring something that forces it to choose? Is the universe actually paying attention to whether we're looking or not?
LUKE: Well, if you can figure that out, sir, there's probably a Nobel Prize in it for you. And when you do, be sure to let the rest of us know.
CALLER: Yeah. I'll call it you back from Stockholm. But seriously, though, I think what got me tonight was realizing how much I want there to be some kind of, I don't know, cosmic loophole. Some way that the stuff we regret isn't just locked in forever. My girlfriend's family is coming over next Sunday, and her mom still brings up this thing I said at Christmas two years ago. And I just keep thinking, man, if only I could go back to that moment.
LUKE: Well, you can take solace in the fact that what happened isn't locked in forever. It's locked in in the past. And the only thing that matters is what is happening today now at this moment because nothing else is accessible to us, even with a quantum eraser.
CALLER: That's, yeah, that's actually kind of freeing when you put it that way. Like, I can't change what I said at Christmas, but I can change how I show up next Sunday. I can be better with her mom this time.
LUKE: That's right, sir. It sounds like we've got to the bottom of the this.
CALLER: Yeah, we did. Thanks, Luke. I'm going to close the laptop and actually get some sleep. And maybe skip the quantum physics videos for a while.
LUKE: All right, sir. Well, thanks for the call. That was an interesting one. Glad we could get it all sorted here. Look at the Roost. Well, thank you for another great show. I hope you all have a wonderful rest of your weekend. I know it's just about over now, and we will talk to you again tomorrow. Bye-bye.
@@ -0,0 +1,103 @@
LUKE: All right, welcome back to the show. My name is Luke. I'm your host for Luke at the Roost. This is the late night talk show where you get to call in and tell me about whatever is on your mind. I'll give you the best advice that I can. Our phone number is 868-919-1188. That's right. The number is 868-919-1188. We're going to go to the phones already. We got Tanya. Tanya's on the phone. How can we help you? Keeping you up at night, Tanya?
TANYA: Hey. Yeah. I'm almost a little nervous about talking about this. I've been drinking way too much lately and I can't really tell anyone in my life about it. You're kind of the only person I can talk to about this shit.
LUKE: Well, at least you know that there's something a little more than recreational going on there. What do you think it is that has your drinking so much lately?
TANYA: I mean, on paper, my life should be perfect. I'm a personal trainer. I help people get healthy all day, but then I come home and just start pouring drinks. I think it's this pressure to be perfect all the time, you know? Like everyone sees me as this fitness role model, but inside I'm just not that person.
LUKE: Well, what person are you inside?
TANYA: Honestly, I'm kind of a mess. I read all these psychology books trying to figure myself out, but at the end of the day, I'm just someone who's tired of pretending. I put on this whole health and wellness front at work, but then I go home and drink half a bottle of wine, sometimes a whole bottle every single night.
LUKE: Well, that's not good. Have you ever had problems with drinking or substances before? Is this a relatively new thing? Has it been leading up to the place where you feel like it's an issue or what's the deal?
TANYA: No, this is pretty new. Started maybe six months ago when I was going through some personal shit, but honestly the fucked-up part is I'm so good at hiding it. I still show up to work at 5am, still train my clients perfectly, still look the part. I just feel like such a fraud, like I'm living this double life. And the more I succeed at work, the more I drink at home. It's like some weird balance thing.
LUKE: How what else could you do at home that's a little bit more productive than the drinking?
TANYA: I mean, I know all the right answers, meditation, reading, working out more, but fuck those things feel like more work, you know, more performing. When I drink, it's like the only time I can just be in this, just be imperfect. I don't have to be Instagram-ready or motivating anybody, it's pathetic, but it feels like the only real part of my day sometimes.
LUKE: But you know, it's not the real part of your day, right? It's a drug that you're consuming to alter your state of reality so that you feel better about what is actually going on in your life. And it sounds like what's going on in your life is everything's great. People look up to you, you're a fitness role model and things are good, I assume you're doing all right financially, and there's just something inside you that's calling out for a drink, calling out to be numbed. And I think you need to identify what that thing is.
TANYA: You just hit something there that kind of scares me because yeah, everything on paper is great, but maybe that's exactly what's fucking me up. I've built this perfect life that everyone admires, but I don't feel like I deserve any of it, like I'm waiting for someone to call me out as a fake. The drinking it's like I'm sabotaging myself before anyone else can, it is that.
LUKE: Actually, I think that's very, very common and it's probably what's going on, but you know what the truth is is that everybody is a fake, we're all fakes all the way up the line. Nobody knows what's going on and none of us deserve a goddamn thing. It doesn't matter how hard we worked, it doesn't matter where we were born or what our family is, the cold, hard reality of it is, we don't deserve anything at all, we're lucky to be here at all. So every day is a good day, take the good stuff that comes and be grateful for it and don't beat yourself up for not being something else.
TANYA: Holy shit, that's actually really fucking liberating to hear. Like I've spent so much time trying to earn this life to prove I deserve it, but maybe I'm just here and that's okay. The drinking though, it's still a problem, I know that, but hearing you say all that, it makes me realize I've been using it to punish myself more than anything else, like I'm drinking because I feel like a fraud, but what? But maybe I don't need to feel like a fraud anymore, if everyone's faking it, if no one really knows what they're doing, then what am I beating myself up for? I still need to deal with the drinking, I know that, it's not healthy and it's not helping, but fuck, for the first time I feel like maybe I can face it without all this extra shame piled on top, does that make any sense?
LUKE: Sure, and you know what, we've all got shame and we've all got pride and we've all got anger and all sorts of issues and all that's okay, but at the end of the day, you're drinking too much, you don't like the way it's made you feel, you know it's not right, you're gonna do something about that, so do whatever it is you gotta do, go to the meetings, join a support group, find another healthy activity, but you can't just keep drinking yourself to sleep every night and expect things to get better, because they won't. Do you know that?
TANYA: Yeah, you're right, I do know that, and honestly, being a trainer, I see it in my clients all the time, you can't just know what to do, you have to actually do it. It's funny how I can be so clear about that with fitness but completely blind about my own shit. I think maybe it's time I get as serious about this as I am, about helping other people get healthy.
LUKE: Alright, I think it is too, so thank you for the call, hopefully you can get off the bottle and do something productive with your time. Alright, who's next? Dennis, Dennis, how's it going? Thanks for calling the radio show, what can we do?
DENNIS: Hey man, thanks for having me. I had the weirdest fucking day, and I need to talk about it with someone who won't judge me. My head's kind of spinning, and I had a few beers just to calm down, but damn.
LUKE: Alright, hit us with it, what happened, what was so crazy about your day?
DENNIS: So I'm doing this landscaping job over in Roland Park, real fancy house, and the clients wife keeps bringing me water all day since it's hot as hell. But then, she starts asking all these personal questions about my workout routine and if I'm single. I'm married and everything, but I'd be lying if I said it didn't get to me a little bit.
LUKE: Well, that's great, you still are desired by the fairer sex, the important thing is that you do nothing with that, if there's a married client's wife hitting on you and you were to pursue that situation, you're probably going to get yourself a busted jaw, and I recommend against that. So take the compliment, you know, it's a compliment, take that, jerk off tonight like any other self-respecting man would do, and leave it at that.
DENNIS: Yeah, man, you're absolutely right, shit. I know better than to mess around with a client's wife, that's just asking for trouble. But fuck, it's just been a while since anyone looked at me like that, you know? My wife's been so busy with her new job, we barely even talk anymore. I took your advice though, went home, had those beers, handled things myself if you know what I mean. Sometimes you just need someone to talk you down from doing something stupid. I'm going to focus on my own marriage, and maybe talk to my wife about spending more time together.
LUKE: I've had a few glasses of wine tonight, just sitting with my f***ing Jesus, that was, okay, Otik, we got to talk to our producer here about what's going on with the phone lines. Brenda, you still there? Brenda? Brenda, it seems like we lost ya.
LUKE: Oh man. Brenda, Brenda, are you there? Do we get you back?
BRENDA: Yeah, yeah, I'm here. Had to turn down the TV, just trying to wind down with some wine after a long day at the clinic.
LUKE: Okay, well, that seems to be the theme tonight, is everybody's getting hammered and calling in. I guess that's what happens on a radio show. What do you wind it down from?
BRENDA: Oh God. Where do I even start? My partner just made partner at their law firm, and I'm still cleaning up cat piss and dealing with angry pet owners for barely above minimum wage. I love what I do, but sometimes it just feels like shit being the less successful one. You know?
LUKE: I actually don't know that's never happened to me, but I can imagine that it's not super fun, but what other, uh, salable skills would you say that you have and, uh, what value do you provide to society?
BRENDA: I keep animals alive for fuck's sake. I've helped deliver puppies I've saved cats from the brink, but try explaining that at those fancy lawyer parties. Everyone's talking about their big cases and promotions while I'm standing there like an idiot with dog hair on my clothes. I mean, I know my work matters, but the paycheck short doesn't show it.
LUKE: No, I bet it doesn't, and that's a hard job to have, I understand, but it's going to be something that you love to do where you wouldn't do it, and it's definitely something that needs to be done. I mean, I have pets myself, and I'm very grateful that there's somebody there to take care of them when they get hurt, but from my side of it, when I get the bill, it looks like there's plenty of money being transferred back and forth, so I'm not sure why you're not getting that.
BRENDA: That's the thing that kills me. I see those bills too, but most of it goes to the clinic owners and the equipment and the drugs. I'm just the skilled labor, making it all happen. And then I go home to my partner talking about their six-figure bonus while I'm still paying off student loans from vet tech school. God, this wine is making me too honest, but sometimes I just want to scream when they talk about their day.
LUKE: I guess I can understand that, but if your partner is getting six-figure bonuses, how serious is this relationship you've got with your partner?
BRENDA: You've been married eight years? And yeah, they're generous with money that's not the issue, but fuck. I want to earn my own way, you know? I don't want to be the charity case who gets nice things because my spouse does well. I want to be someone at those firm parties, not just the plus one who works with animals. Does that make me sound like a terrible person?
LUKE: No, it doesn't make you sound like a terrible person, it makes a lot of sense, actually, but you have to provide something there to... Do you care? Why do you care about the firm parties? It sounds like you think it's boring work and you're sick of hearing about it, so why are you even going?
BRENDA: Holy shit, you're right. Why the fuck am I torturing myself at these stuffy parties? Like I literally dread them but keep going because I think I should. I'd rather be home with my dogs and Netflix. Maybe I'm making myself miserable trying to fit into a world I don't even like. It's just... I think I wanted to prove I could belong there, but maybe I don't want to.
LUKE: Yeah, the truth is, we don't belong everywhere, and that doesn't mean what you do isn't valuable, even though you don't get as much money as a lawyer does. You didn't have to go to school for as long either as the lawyer did, and that's kind of the deal. You can provide value in lots of ways just because you have a day job as a veterinarian or a veterinarian's assistant or whatever the fuck it is that you do. You can have other fulfilling hobbies, you can write a book, you can do improv, you could play music. There's plenty of other ways to derive value from the world than just money, and you have to find what works for you.
BRENDA: I really needed to hear that tonight. Shit, you're making me cry a little. I've been so focused on measuring up to their world that I forgot I used to paint. I used to do all kinds of things before I got caught up in this comparison bullshit. And you know what? I'm fucking good at what I do. Those animals trust me. Their owners trust me. Maybe I need to get my head out of my ass and stop trying to be a nond.
LUKE: Yeah, you do. Get your head out of your ass and do the right thing, you know? Lay off the, lay off the wine and never makes anything better. Okay, let's take one more call, and let's hear from Dennis. Dennis, thank you for calling the radio show. What's going on in your life? Fight you calling.
DENNIS: Hey, man. Thanks for having me. Been listening to the show for a while. I've got this thing I need to get off my chest from earlier today. I'm a little buzzed, so I might be more open than usual.
LUKE: All right, there, Dennis, what's going on?
DENNIS: Well, I do landscaping, right? And today I had this job at this gym doing the bushes out front, and I went inside to use the bathroom and shit. I ended up hooking up with this dude in the locker room. I'm not even gay at least I don't think I am, but it just kind of happened.
LUKE: Okay, dude, well, if you hooked up with a dude in the locker room, there might be a good chance that you might be at least a little bit gay. And you know, no big deal, that's kind of strange, you're just doing your job and hook up with a dude in the locker room, like on the job at work. It's a little bit extreme, don't you think?
DENNIS: I know, man. I know. And while never done anything like that at work before, but this guy was just checking me out while I was washing my hands, and next thing you know, we're in the shower stall and fuck, you're right, though. I fooled around with guys before just never thought of myself as gay. Maybe I'm by or something. My head's just spinning about the whole thing.
LUKE: Well, did you prefer it over the company of women?
DENNIS: Honestly, it's different, but yeah, I kind of did. The thing with guys is it's just more direct, you know, games, just straight to what we both want. But I still love women, too. I just can't believe I did this shit during work hours. My employees were out there trimming hedges while I was, getting you into the picture.
LUKE: All right, well, that's a little bit strange and definitely unprofessional, but it sounds like you, sir, are gay, and that's okay. I'm glad you had a good time in the locker room with your, with some strange dude. I hope you used protection and maybe next time, do it off-hours.
DENNIS: Yeah, man, you're right about all of that. I was safe, not totally stupid. Just got caught up in the moment, you know? But fuck, doing it during work hours was definitely not my proudest moment. My crew probably wondered where the hell I disappeared to for so long. I should probably stick to Grindr on my own time from now on.
LUKE: Yeah, well, if you're playing on Grindr, you're gay and you know it, so, uh, good for you. I'm glad that you just happened to have protection with you when you were gonna fuck a random dude at the gym, but hey, if you had a good time, nobody got hurt. Congratulations, model top.
DENNIS: Now, I never said I was on Grindr, this was totally random, and I got the condom from the vending machine in there. I'm not that prepared, but fuck, maybe you're right. Maybe I've been fighting this side of myself for too long. It just feels weird saying it out loud, you know? Like, I've got this whole macho business owner thing going on, but then here I am hooking up with dudes in gym.
LUKE: Hey, you know what I always say, uh, if it feels weird saying it aloud, you should call into a radio show and tell the whole world.
DENNIS: Shit, when you put it that way, it does sound pretty crazy. Here I am telling all of Baltimore about my gym hookup, but fuck it. I'm three beers in and it feels good to just be honest about it. Better than keeping it bottled up and freaking out alone in my truck like I was doing earlier. Thanks for letting me get this off my chest, man.
LUKE: Yeah, no problem, that's what we're here for. Oh, right, so it looks like we've come to the conclusion of the show, tune in tomorrow for our next batch of guest advice. Thanks for listening to Luke at the Roost. Let's Luke at the Roost, and remember, if you have protection and you need a dude at the gym and you fuck them in the ass in the shower, you're probably a game-in.
@@ -0,0 +1,289 @@
LUKE: All right, welcome back, ladies and gentlemen. My name is Luke, and I am your host of this radio show, Luke at the Roost. It's the radio show where you can call in and tell me what's going on in your life, and I'll give you the very best advice that I can. If you'd like to call in and get some advice, the number's 208-439-583. That's 208-439 Luke. locked in the back of a RV and you don't want to draw attention to yourself, you can send us an email at submissions at lukeatsderoose.com. And if we deem that it's funny enough, then it may be played on one of these next year episodes. Tonight is a very special night. It's Monday, February 23rd, here in animus, New Mexico. And this is the 20th episode of this we have a new segment. The segment's called Storytime with Luke, and this is what it's about. Storytime with Luke. Storytime with Luke. Okay, so this is Storytime with Luke. And today's story, I've been sitting at my computer here for 16, 18 hours of, day for the last couple of weeks and not getting a whole lot of activity or moving. And my dog, Elvis, the king here, has been looking at me like, what the hell, man? When are we going to go do something fun? So I decided today that it was a nice enough out to go for our regular scheduled walk up to the mercantile store and around and back. And today on our walk, something special happened. So Elvis, when Elvis takes a piss, he squats down like a girl. And I try to tell him, Elvis, all the other dogs in town are going to make fun of you. If they catch you doing this, you should really be lifting your leg like a male dog. So today, on our walk, I saw him find a nice little bush and lift his leg. And then I was so proud. I filled up with joy and pride. And he lifted up his leg and took a shit with his leg waving the air. Storytime with Luke. Storytime with Luke. All right. Well, that's Story Time with Luke. I hope you liked it. We're about to get to the calls. I'm going to start some music here. Here we go. A little ambiance. And first up on the line here, we have Donnie. The show, how can we help you today? What's going on in your life, sir?
DONNIE: Hey, Luke. Yeah, thanks for taking my call. So I found out about two hours ago that my dad had a whole second family I never knew about.
LUKE: Like he had a second family? Like brothers and sisters and stuff? Or you have a second family? Like brothers and sisters and stuff.
DONNIE: No, I have brothers and sisters I never knew existed. My dad died three months ago. And tonight, some woman in Tucson emails me a photo of three kids who apparently grew up calling him dad, same as I did.
LUKE: Okay, well, can we assume that that email came from their mother?
DONNIE: Yeah, yeah, it did. She said she thought I should know now that he's gone, that her kids have been asking about me, which is wild because I didn't even know they existed until 7 o'clock tonight.
LUKE: Yeah, that's a pretty weird thing to feel, what are you going to do about that? Are you going to reach out to them and try to start a relationship or just let it be?
LUKE: I don't think it's the big things that they care about so much.
EBONY: I got in my head tonight thinking it wasn't enough, that I wasn't enough, because I can't...
LUKE: Well, you know what? You're doing great. Those donuts on Saturday morning, that's exactly the kind of memory that's going to stick with them forever. That's their version of learning to drive in the Kmart parking lot. And it's perfect because it's yours. It's what you can do, and you're doing it consistently. That's what matters.
EBONY: Yeah, yeah, I actually do that already. Every Saturday morning, we go to this little donut shop, and they each pick one, and we sit there and talk about their week. It's like five bucks total, and they love it. I just, I don't know. I got in my head tonight thinking it wasn't enough, that I wasn't enough, because I can't...
EBONY: I mean, they might in the moment while they're young, but when they get older and they can look back and see what you did give them, they'll much appreciate donut Saturday or Donut Sunday or whatever it was over. My mom gave me this fancy car. If you want to make more money and be able to do those bigger things, that's an option too. You can do those things, but I think probably it would take more of your time and attention away from the kids and make those gifts a lot less meaningful. You know what's messed up? I heard from Donnie earlier, the guy who called about his dad having the whole second family, and I was sitting here thinking, at least my dad was there. At least he gave me his time. And then I turn around and worry I'm not giving my kids enough because I can't buy them a truck someday. Like I'm completely missing what actually mattered about my dad.
LUKE: Yeah, don't worry about it. You just got got by a commercial. That's what they're designed to do, it's doing a very good job on you. So don't worry about it. You're enough for your kids. Give them as much as you can in your time and attention. Spend time with them and have those special days, and that's what they're going to remember when they get older.
EBONY: Thank you. I really needed to hear that tonight. I'm going to finish this wine. Look at some more space pictures and actually get some sleep before I have to open tomorrow. And maybe I'll take the kids for donuts a day early this week.
LUKE: Excellent. I think you could have double donuts this week. And if you want to go for extra credit, you could teach them to do donuts in your car before it goes to the junkyard. So that's what I recommend. And don't worry about it too much. Stay off the commercials. Speaking of commercials, it's time for an ad from our sponsors.
This episode is sponsored by Shoe Spray Co. It's to With spray you use in rental bowling shoes, because bowling is a game, but these shoes are gross. Let's be honest, those shoes aren't dirty. They're used. They've had shifts. They've been through birthdays, work parties, and at least one guy who insists he's actually really good at this. Shoospray code doesn't promise fresh linen or spring rain. Nobody believes that. No one wants to be lied to while lacing up rental shoes. Shoe Spray Co just handles the situation. It makes them tolerable enough that you can focus on the important part, pretending you're not competitive and then getting mad anyway. Use code strike at shoe spray co for 15% off. Shoespray Co. Reality-based footwear management.
All right, and we're back. We're back just in time to hear from Murray. Murray, welcome to the show. You're calling in on our 20th episode. Congratulations. What would you like to talk about? Let's make it a good one.
MURRAY: Hey, Luke. Thanks for taking my call. So I got this envelope from Lab Corp sitting on my kitchen table right now, and I'm pretty sure it's going to tell me whether or not I'm a dad to a three-year-old I've never met. My ex-girlfriend called me three weeks ago out of nowhere. We broke up about four years back. She moved to Tucson, and apparently she's getting married now. Her daughter's three, and the timeline lines up with when we were were still hooking up sometimes, so she needs to know for sure.
LUKE: All right, she didn't think about maybe mentioning that while you were still involved together. That's a bit odd, and I'm sorry to hear that. That's my worst nightmare right there.
MURRAY: Yeah, well, here's the thing. She says she wasn't sure herself until recently. We were on and off for like five years, total mess of a relationship. And I guess there was another guy in the picture She thought it was his. He thought it was his. They tried to make it work for a while. But now the kid's getting older and apparently doesn't look anything like him. And her fiance wants everything settled before they get married. So here I am. Staring at this envelope, I'm too chicken shit to open.
LUKE: Why don't you open it right now? Live on the air?
MURRAY: Oh man, I don't know if I can do that. I've been sitting here for. I've been sitting here for for two hours just looking at it. The thing is, Luke, and this is what's really messing with me, I've already been playing both scenarios out in my head for three weeks straight. Like, what does my life look like if I open this and it says yes? What does it look like if it says no?
LUKE: Well, I can tell you right now, if it says no, your life looks exactly the same as it does today. If it says yes, then your life looks completely different.
MURRAY: That's exactly it. And here's the part the hell out of me. I think I wanted to say yes. I'm 36 years old. I teach eighth grade social studies in Lordsburg. I go home to an empty house every night. I've been thinking about this little girl for three weeks, wondering if she likes dinosaurs, or if she's scared of the dark, or what her voice sounds like. My ex sent me one picture, just one, and I've looked at it about a hundred times trying to see if she has my nose or my eyes or anything.
LUKE: Well, that's not going to do anything for you because people have eerie similarities and it's possible that she got other genes that give her a different nose from both of you, so I don't know that that's going to do it for you. You're going to have to open the letter to find out, but the way I see it is if you want it to be true, then you can't lose. Open it up.
MURRAY: You're right. You're absolutely right. Okay, hold on. I'm opening it right now. My hands are shaking like crazy. Okay. sharing it open.
LUKE: All right. Good for you. What does it say? What does it say? Everybody's waiting.
MURRAY: It says, probability of paternity, 90 to 9.9%. I'm her dad. Oh my God, I'm her dad. Oh, my God, I'm her dad. Her name is Sophia.
LUKE: Well, congratulations to you and your new toddler-aged three-year-old daughter. That's very good news, and we're all happy for you. So now you've got to get to get yourself another room and turn it into a little girl's room and she can come over on the weekends and everybody's going to laugh and laugh and laugh.
MURRAY: Yeah, except she's in Tucson and I'm here in Lordsburg, and her mom's getting married in like two months. I don't even know what my rights are, if I have any. I haven't been there for three years, Luke. Three years.
LUKE: Yeah, well, you didn't know, and she didn't tell you, and there's no way you could have known, but you can definitely fight for the those rights. And it sounds like she wanted you to know because she reached out and told you about it. So talk to her, see what the arrangement's going to be. And then if you have to go to the courts to get some level of custody of your child, then go do that. But yeah, congratulations. Happy day.
MURRAY: Thank you. God, I need to call her right now. I need to call Veronica right now and tell her. Thank you, Luke.
LUKE: You're very welcome. I'm glad I could help. Next up on the line we have Debbie. Debbie, welcome to the show. What's going on for you on this wonderful Monday evening?
DEBBIE: Hey, Luke. So I bought a house six months ago, and I'm sitting on the bedroom floor right now in my coat, because I sat in the driveway for like 45 minutes tonight, and I just realized I don't actually want to be here.
LUKE: You don't want to be where? You don't want to be in the house, or you don't want to be in the area you're in, or where is it you don't want to be?
DEBBIE: In the house. Like, this is the first real thing I ever owned, you know? I'm 21. I bought it myself, with money I made selling jewelry and vintage stuff online, and I should be so proud of it. But I pulled into the driveway tonight after working all day, and I just sat there with the engine running because going inside felt like this weight dropping on me. And now I'm inside, and it still feels
LUKE: Well, you know what? I think the crushing weight that you feel dropping on you is called a mortgage. Yeah, mortgage. That's part of homeownership and that's the part that never feels good. So here's what I recommend to you.
LUKE: Since you just bought the house, congratulations, it's a good thing to own property, especially at your age. I think you should live in that house for a year or two. See what it's like to be a homeowner and get used to utilities and small repairs and stuff like that. later, after some time that you don't want to be there, you can either rent the house out and be a landlord, or you could sell the house at probably a significant profit, usually. So that's what I think you should do. Enjoy the house while you're there. You can do anything for a couple of years, so just live there. And then when you're ready to move on, move on.
CALLER: No, but see, that's the thing, though. It's not the mortgage. I make good money like really good money. figures from my Etsy shop. I work harder than anyone I know.
LUKE: Great. It seems like you've got a nice place to work on your Etsy shop for the next couple of years then.
CALLER: Luke, I'm working like 16 hour days. I'm refreshing my shop stats at two in the morning. I haven't taken a weekend off and I don't even know how long.
LUKE: Well, that sounds like your problem. So if you're making enough money, you've got your house, your mortgage is covered. you're not worried about the money, then take a weekend off and unwind. That's a stressful situation buying a house and you're very young, so that's a big decision, and it's going to affect you. So take some time for yourself and re-center your whole outlook on things. And if you still feel like you don't want to be in the house in another six months, then get out of it. There's plenty of ways to do that.
CALLER: Yeah, I know I need to. I know I need to. I know I know I I should hire help, take time off, all of that. But hearing you say it out loud makes it feel more real, you know? Like I can't just keep telling myself, I'll slow down next month.
LUKE: Nope, but it sounds like you've made good progress and congratulations on the success of your business and your new home ownership. And if you're finding out that's not the life you want, then at any point you can turn about face and go for a different one.
CALLER: You're right. Thank you, Luke. I appreciate it.
LUKE: No problem. That's what we do here. Next up we have Darnell. Darnell, what's going on in your world today, sir? How can we help you at Luke at the Roost?
DARNELL: Man, so I'm sitting in my garage right now, surrounded by all these boxes I pulled down. And I just found these old demo tapes from like 2004, 2005. Me and my cousin Marcus, we had this whole hip-hop. thing going. And Luke, I'm telling you, listening to these tracks right now, they're actually good, like really good. Better than half the stuff that's on the radio these days. And here's the thing that's got me twisted up. Last week, my captain at the station, Rick, he pulls me aside and says the department might not make budget next year.
LUKE: Okay, well, congratulations on finding your old records. You know what? I think most of us have a box full of old records that's better than what's on the radio right now. people with records that are better than what's on the radio right now. And as far as the department not making budget, that's pretty much every department right now. So you're in the same boat as literally everyone.
DARNELL: Yeah, no, I hear you. I'm not special. It's just, okay, so I've been a firefighter for eight years now.
LUKE: And you're not going to make budget? What does that mean for you? Does that mean you lose your job, or you have to take a pay cut, or you're not going to get the hours you expect? What is... the budget, the department being out of money mean to you.
DARNELL: It means they might not hire the next class, which means the guys who were supposed to move up, they're stuck, which means I'm stuck. I've been waiting on a promotion for two years now, and Rick's basically saying it's not happening anytime soon. Maybe not at all if things get worse. So I'm looking at the same position, same pay, for who knows how long.
LUKE: Well, I don't know that I've ever seen a plan to make things get not worse. Worse seems to be the default trajectory of everything. So I would expect things are probably going to get worse, and you're going to be locked at the same pay while you're in that same area in that department. So if you would like to move up in the fire department, you may have to move to another location that has more of a budget to pay you.
DARNELL: Yeah, and that's the thing I keep coming back to. I could apply to Phoenix, Scottsdale places that are hiring. But I've got a mortgage here. got her job at the hospital. And honestly, man, I like my crew. I'm good at what I do. But sitting here tonight listening to these tracks, hearing myself at 20 years old, before I had all this locked down. I don't know. It's like I can hear this version of myself that still thought anything was possible, you know?
LUKE: Nothing's locked down. Anything is still possible. If you want to pursue a hip-hop career, you can do that. You're probably not going to do so well in the there's plenty of people with great hip-hop records, but you can give it a shot. It has been done. Maybe Dr. Dre'll come down into a rap battle and discover you.
DARNELL: Ha, yeah, Dr. Dr. Dr. Dr. Trail at the VFW Hall. Look, I'm not delusional. I know I'm not going to be a rapper at 34 with a mortgage.
LUKE: It is very unlikely, but it's not impossible. And it sounds like if the mortgage is a problem, then you can. You can get rid of that. That's not locked down. You can always sell a house and the wife likes her job, but she can continue to like her job and live somewhere else, or you can talk to her and explain to her that you're stagnant and you need to make a move and talk that out with her and see if it's a possibility. If you really want to go thermonuclear, you can blow up your entire life and start over in a van by the river.
DARNELL: Right, right. No, it's not even that I want to blow everything up. It's more like Okay, so Marcus moved to Flagstaff in 2006, got into construction, and that was it for the music. And I went hard at the fire academy. Got the job, bought the house, did everything right.
LUKE: Okay, well, you did everything right for you at the time, and it may not be what's right for you now. Or it may be. This might just be a strange feeling you're having that you could be having a different life. But there's a couple of things here. If you want to pursue music, you can do that either as a career or just for fun. There's plenty of people that would collaborate with you on a hip-hop project, I'm sure. And it's never been easier than it is today to make music or art or really anything. So you can do that if you want on the side. And if you need more money, you're stagnant, you're stagnant at the department. Then you have to change what county you're in and go work for a department that's funded.
DARNELL: Yeah, you're right. I could just make music again without it being a whole career third. thing. I haven't even talked to Marcus in like six months. I could call him up, see if he's still got any of his equipment. The thing is, I think what's really getting to me is I spent three hours out here tonight, and I felt more alive than I have in months.
LUKE: Yeah, I used to have a box full of CDs like that, too, of all my demos and songs that I wrote. And what I realized, after carrying them around for 20 years in a giant box full of unmarked stuff, is that I don't even own a Nobody cares. So I threw them away. If you want to make music, make music for you. If you want to make a career out of it, then you got to really buckle down and make a career out of it. And if you need more money, then you've got to find another job. That's all there is.
DARNELL: Man, that's harsh. But you're probably right. I've been carrying these tapes around the same way, not even listening to them, just knowing they're up there. And tonight, I finally pull them down, and it's not. Like, what was I waiting for? Permission? The money thing, though. That's real.
LUKE: Yep, the money thing is real as it is for most people.
LUKE: So you're going to have to figure out a way to make enough money to support your mortgage if that's what you want to do. And I'm just going to go out on a limb and say a rap career probably isn't it?
DARNELL: Yeah, no, you're right. I mean, I knew that before I call. I think I just needed to probably Phoenix or Scottsdale. Get on with a department that's actually funded. Keep the house. Keep the benefits. My wife and I could handle the commute for a while, or we sell and move closer.
LUKE: You can absolutely do that for as long as you can stand it. A commute like that will get to anybody's nerves over time. But for now, you can do it. You could rent out the house and get yourselves an apartment in the city or do the commute.
DARNELL: Right. Yeah. Rent it out. smart. Cover the mortgage that way. Get a place closer to wherever I end up. And I can still mess around with the music on weekends. Just for me.
LUKE: Yes, sir. You most certainly can. That's the way to do it. And make sure that you're fulfilling all areas of your life. And music is definitely one of those.
DARNELL: Yeah, yeah. I appreciate that, Luke. I think I just needed to hear someone cut through the bullshit.
LUKE: Well, that's what I do. I cut through the bullshit. like a hot knife through cheese. And, uh, okay, so I am obligated now to play another one of the words from our sponsors. Let's talk about mental health. Specifically, let's talk about brutal honesty, the therapy app that's tired of your shit. Unlike other apps with their calming voices and gentle affirmations, brutal honesty pairs you with an AI therapist that tells you what your friends are too nice to say. Did you try journal? about it? No, we're not doing that. Our algorithm analyzes your patterns and asks questions like, why do you think you deserve to feel better when you don't even drink water? And is your anxiety real or did you just have four cold bruise on an empty stomach? Brutal honesty, because coddling yourself got you here. First session is free, but it will hurt your feelings. Use code grow up at checkout. Brutal honesty, the app that blocks itself if you don't do the work. Okay, and we're back here with Roberta on the line. Roberta, welcome to the show. How can we help you?
ROBERTA: Hey, Luke, thanks for taking my call. So I'm sitting here in the dark at my thrift store, and I just found out my business partner emptied our bank account three days ago and took off with everything.
LUKE: How much was everything?
ROBERTA: $42,000, which was supposed to cover payroll, rent, utilities, our spring inventory. by. I had two employees show up this morning, and their checks bounced. I've been staring at this screen showing zero for the past hour trying to figure out how I'm going to tell my husband that the money we put into this place is just gone.
LUKE: Have you, uh, reached out to the business partner, I assume?
ROBERTA: I've called her probably 30 times since Friday. Straight to voicemail every single time. I drove by her house yesterday and her car's not there, mails piling up. She's just gone. She's just gone.
LUKE: Well, I don't know that $42,000 is enough money to disappear for any significant length of time, so it's likely she'll be back.
ROBERTA: That's what I keep telling myself. But Luke, she knew exactly what she was doing. She waited until Friday, after the banks closed, cleaned out the account, and by Monday morning, we're dead in the water. This wasn't some panic move. And the thing is, we've been friends for six years. We started this story. We started this story together from nothing.
LUKE: Well, you both had your names on the accounts, which means she was within her rights to withdraw that money. So I don't think that you have any legal recourse to go after it. Maybe you could try to sue her in civil court, you know. But I don't know. I'm not a lawyer. I would think that probably you're screwed on this one and you're going to have to come up with another way to raise that money to cover your payroll.
ROBERTA: Yeah, I figured. That's the part that's making me sick. I keep thinking. I keep thinking about Darnell earlier. You know, stuck because of budget cuts. At least he's got a steady paycheck. I've got two employees who depend on me and I can't even make good on what I owe them. One of them is a single mom.
LUKE: Well, that is rough. And I think, um, maybe have you told your employees yet what the situation is?
ROBERTA: I told them their checks bounced, but I made up some story about a bank error. Said it would be fixed by Wednesday. I couldn't look them in the eye and tell them the that I have no idea when I can pay them or if we're even going to stay open.
LUKE: Well, it sounds like maybe you shouldn't stay open if you don't have the operating expenses to sustain the business, but you should let them know the truth so that they can make alternate plans. And also, you got to tell your husband what's going on because that affects him.
ROBERTA: You're right. I know you're right. It's just Danny and I put 28,000 into this place when we opened. That was our savings. Money we were supposed to be supposed to use to finally fix the roof. Maybe take a real vacation for once.
LUKE: Yeah, well, that's a lot of money to lose, but it seems like you lost it. And I hope that in this experience, you've learned a valuable lesson, which is never trust anybody. If you're going to go into business with a partner, make sure that everything is well-buttoned up by a lawyer, and that you both need to sign the checks in order to take the money out of the accounts.
ROBERTA: God, that's dark. But yeah, I guess that's where I'm at now. Six years of friendship. Gone. The crazy thing is, she was the one who was always so careful about the books. Always worried about every penny. I trusted her completely. I never even thought to check the account myself until it was too late.
LUKE: Well, I mean, it's only been a few days. There could be a reasonable explanation. Maybe she had her mother kidnapped and had to pay a ransom in Bitcoin or something like that, and it's the only thing she knew how to do. There could have been good intentions behind this. Probably not. Probably she ripped you off and ran. But, but we don't know that until you talk to her.
ROBERTA: I haven't thought about it like that. I mean, I've been so angry I just assumed the worst. But you're right. I don't actually know.
LUKE: I mean, you can let the police know. You can let the bank know. I don't think that they can do anything about it at this point, but you should still let them know that there was some kind of misgoings on.
ROBERTA: Yeah, I called the bank this morning. said since her name's on the account, technically she didn't steal anything. It's a civil matter, not criminal. So I'd have to sue her, get a lawyer, spend money I don't have to, maybe get back money I'll never see again.
LUKE: Yeah, well, I mean, there is small claims court. I think that only goes up to like 15 grand or something like that, but at least it's something. It forces her to show face, so it'll bring her back to the area if she's left.
ROBERTA: That's true. I didn't think about small claims. I didn't think about At least I could get her in front of a judge. Make her explain herself. Even if I don't get all the money back, maybe I'd get some answers.
LUKE: Yeah, I don't think you need a lawyer for small claims court. I did it once, actually. One summer night, I went out looking for my backpack in my car and it was gone. So somebody had stolen it. So I went to the police. I let them know. Somebody stole my stuff. And then I found, I was searching through Craigslist. I found somebody selling some of the stuff that was in my backpack. So I went back to the police station and we set up a sting operation where I offered to buy back some of the gear that I had had in the backpack and meet a dude in a CVS parking lot. And the cops set up across the street to see the transaction go down. And that proved that, you know, the items were stolen for me and I had to go to small claims to look for restitution. I never did get the money, but he was ordered to pay it.
LUKE: Wait, you set up a sting operation over a backpack? That's incredible. I can't even get my partner to answer her phone.
CALLER: But yeah, okay. So even if she's ordered to pay, there's no guarantee I'll actually see the money. That's what I figured. Well, no, I set up a sting operation over the items that were inside the backpack.
LUKE: But I think you will see the money if you push it.
CALLER: I didn't. It was only a couple hundred bucks where there wasn't worth any more of my time. But I probably could have gone back to the court and enforced his, I don't know, probation fees or something to be diverted to me. I don't know how any of that works.
LUKE: Right. Okay. So there's ways to actually collect if you stay on it.
CALLER: I mean, we're talking about enough to cover payroll for two people, so it's worth my time. I just hate that it's come to this.
LUKE: Well, it takes a long time, so it's not going to cover your payroll right away. If you need to cover that payroll, you're going to have to find another way alone or, you know, talk to friends and family and see what you can do to raise the money. And stop operations because you can't afford to pay them going forward.
CALLER: Yeah, you're right. I've been sitting here like if I just figure out the legal stuff, the money will magically appear. But those employees need to get paid this week, not six months from now when a judge orders it. I'm going to have to tell Danny tonight. See if we can scrape together enough between savings and maybe his sister.
LUKE: Yeah, that sucks. I'm sorry that you ended up in that situation. It's a hard place to be. And I don't envy you at all, but that's the right thing to do. I think that's the correct course of action. And hopefully it all works out. And she comes back and returns the money. But I wouldn't bet on that. For now, I'd start looking at other arrangements.
CALLER: Yeah, I'm not holding my breath for her to suddenly grow a conscience. I appreciate you talking me through it. I'd just been spinning my wheels all day. Couldn't even Think straight. At least now I know what I got to do. Even if it sucks. Thanks, Luke.
LUKE: You're welcome. That's what we do here. Hopefully it all works out. And you get your money back and you're able to pay your employees and continue with your thrift shop business. Next up on the line we have Phil. Phil, how you doing, buddy?
PHIL: Hey, Luke, I'm all right, man. So I've been sitting here at the laundromat for like two hours now. And I cannot stop thinking about that Zachary Ty Bryan thing you mentioned earlier. You know the Home Improvement Kid getting locked up for violating probation?
LUKE: I do not. I have no idea what you're talking about. Are you talking about the television show Home Improvement from the 90s with Tim Allen, one of his kids?
PHIL: Yeah, yeah, the middle kid. Jonathan Taylor Thomas's TV brother. I read about it today. And it's been stuck in my head. Because my cousin Danny's sentencing is on Wednesday. And I'm supposed to text to testify against him tomorrow. Well, not against him exactly. But I saw what I saw behind the Circle K back in November. And the prosecutor keeps calling to make sure my story stays straight.
LUKE: What is it that you saw behind the Circle K?
PHIL: I saw Danny breaking into the storage unit back there. It was like two in the morning. I was coming back from dropping off my girlfriend at the time, and I see him with a crowbar prying open the lock. And the thing is, he looked right at me. eye contact. We grew up together, man.
LUKE: Okay, and how did you end up on the telephone contact list for the prosecution?
PHIL: I called it in that night. I sat in my truck for like 10 minutes, just watching him load stuff into his car. And I kept thinking, maybe I should just drive away, you know? But then I thought about the guy who owns that Circle K. Jose, he's always been decent to everyone. And I just called 911.
LUKE: All right, so that's a little thing we in the business like to call snitching, and it's never a good idea to be the snitch, as they say. So do you have plans on how you're going to navigate that in a post-snitch world?
PHIL: That's the thing that's eating me up, Luke. Danny's mom has left me like eight voicemails today saying Uncle Hector would be ashamed of me. And that one hits different because Hector died last spring. He was the one who raised both of us half the time. Let us run wild on dirt bikes every Saturday. And now, I'm driving the same routes through Lordsburg and Amas for UPS that we used to tear up as kids. And everyone knows. Like I went into the gas station on Friday, and the guy behind the counter wouldn't even look at me.
LUKE: Yeah, I mean, you must have expected that when you ran your mouth about what you saw behind the circle K.
PHIL: I mean, yeah. I knew it wasn't going to be good, but I guess I thought doing the right thing would feel different than this. Like maybe people would understand that Danny's been spiraling since Hector died, stealing copper wire, breaking into storage units, and somebody had to pump the brakes. But instead, I'm Nerat, and he's the victim. And his mom is blowing up my phone talking about family loyalty while her son is out there destroying himself.
LUKE: Yeah, well, there are other ways you could have handled that. I mean, you turned in your own family to the police. That's pretty, uh, uh, nobody's going to respect that very much. You could have, uh, uh, uh, you could have gone up to him on the side and let him know that you saw that and not to continue doing it or, you know, giving him the chance to stop in his tracks. But that's not what you did. You just called the police. So, yes, you are a rat.
PHIL: You're right. I could have walked over there that night and said something. I sat in my truck for 10 minutes. I had time. And the worst part is, Danny would have listened to me. We've been tight since we were kids.
LUKE: Well, the way I've seen. You have two options. You can go in there and testify the truth and let them know what you saw and rat out your family, or you can, you can, uh, not tell the truth and lie under perjury and, uh, give up yourself that way. So either way, it's not the best situation to be in.
PHIL: I already gave a statement to the cops that night, though. The prosecutor called today to prep me, make sure my story stays consistent. So if I get up there Wednesday and sell, Suddenly I didn't see what I saw. They're going to know I'm lying. And then I'm looking at perjury charges on top of being the guy who snitched and then trying to take it back.
LUKE: It doesn't matter if they know you're lying. It only matters if they can prove you're lying. And it was dark. It was rainy and you would you were tired that night and who you saw was actually not this person.
PHIL: Man, that's the problem though. It wasn't dark and rainy. It was like 9.30 on a clear night and I was parked maybe 30 feet away. I watched him for 10 minutes. The prosecutors got my original statement where I described everything. What he was wearing. The stuff he was loading.
LUKE: Sounds to me like you're not listening to me, sir. It was dark, and you're not sure what you saw. You called the police, uh, in haste.
PHIL: You know what? You're right. I hear you. It was dark. I was tired after a long shift, and I shouldn't have been so sure about what I saw from that distance. Maybe I jumped the gun calling it in like that.
LUKE: Maybe you did. I would be careful if I were you on account of the whole stitches thing, because you know who gets stitches. And next up on the line we have Adrian. Adrian, thanks for calling in. What's going on with our callers today? We're, uh, we've got some not very interesting calls. Uh, can you help us out?
ADRIAN: Oh man, I hope so. So I'm sitting here cutting up Hatch Green Chile for a birth tomorrow. My client wants a pot of stew ready for after she delivers. And I just need to say something about commitment that's been driving me crazy all night. I was at the diner earlier and this tourist couple ordered Christmas-style enchiladas. You know red and green Chile.
LUKE: Sure.
LUKE: So, you're working with a woman who's given birth and her demand for post-birth snack is a chili soup?
SARAH: Stew, not soup. And yeah, she's very special. specific about it. She's having the baby tomorrow. I'm her midwife. And she swears, she needs hatch green Chile stew waiting for her after delivery. But that's not why I called. What's bugging me is this whole Christmas thing. The waitress winked at me when they ordered it, like we were in on some joke together, and it just set me off.
LUKE: Do you hate Christmas?
SARAH: No, I love Christmas, the holiday. I'm talking about Christmas-style enchilada. I'm talking about Christmas-style enchilada. red and green chili together. It's what tourists order when they can't commit to one or the other.
LUKE: Do you know what tourists do? By definition? They come into your town and they spend money and promote businesses and make sure that everybody in the area that, you know, works there, gets paid so that they can eat. So what seems to be the problem with this red and green chili?
SARAH: Okay. Fair point about the money. But here's what's getting under my skin. Choosing both isn't really choosing at all. It's hedging your bets. It's saying, I can't commit to red or green, so I'll just take everything. And I've been thinking about commitment a lot lately because my second ex-husband just got engaged after six months to someone who probably orders Christmas every single damn time.
LUKE: Um, well, that's not necessarily what it means. It could also mean that somebody likes both red and green chili and wants to have them both. Uh, which is a perfectly acceptable way to eat an enchilada.
SARAH: You're right. It is acceptable. I know I'm being irrational here, but it feels like a metaphor for my whole life right now. I'm a midwife. I'm a photographer on the side. I caught this perfect shot of a barn owl on my drive home tonight. And I keep thinking about how I wait for the right moment with photography.
LUKE: Yeah, well, photography is a fun hobby, and to get the shot, you often have to wait for the right moment or identify the right moment. right moment while it's happening. And that is a skill.
SARAH: It's more than a hobby for me, but yeah, and that's what I'm good at. Patience, waiting for the right moment instead of just taking whatever's in front of me.
LUKE: Okay, and what does that have to do with this chilly situation?
SARAH: Because I didn't wait with either of my marriages. I just jumped in, took what was there, didn't really choose deliberately. And now my second ex is engaged after six months to someone else. And I'm sitting here at my kitchen table at midnight, chopping green chili for a client stew. And I'm wondering if maybe I'm the one who should have been ordering Christmas all along.
LUKE: Here's what I say. If you like red chili, buy the red chili enchilada. If you like green chili, buy the green chili angelata. If you like both chilies, or you'd like to try a little bit of each, buy the Christmas enchilada. And that's all there is to it. It has nothing to do with, uh, your midwife chili stew or your failed marriages.
SARAH: You're absolutely right. God, I called in about enchiladas like a crazy person. I heard Roberta earlier talking about her business partner cleaning out their account. And that's a real problem.
LUKE: Yeah. I understand enchiladas are very important. And so are green chilies, especially around hatch. So I can see where you derived all. all meaning in your life from metaphors of chili colors.
SARAH: Okay, now you're making fun of me. Fair enough. I deserve it. I just, I think what I'm actually upset about is that he moved on so fast. Six months, Luke. We were together for three years, and it took him six months to find someone he wants to marry.
LUKE: Yeah, well, I mean, I was married a lot longer than that, and it took her a lot less, so you're in good company. and it makes no difference to you. It doesn't matter. Let them all be happy. Let them all eat chilies. That's what I say. Let everybody eat chilies and you just focus on what you've got in front of you, which is a birthing mother who wants her goddamn stew.
SARAH: You're right. She's due any day now and she's been so specific about this stew. It has to be hatch green. It has to have pork shoulder. It has to simmer for at least four hours. And honestly, I love that about her. She knows exactly. what she wants.
LUKE: Hey, that's good for you. I mean, if somebody, if one of my clients was like that, I would be like, you have to make your own stew. Um, but if you can, if you can deal with that, good for you. Give her the stew that she deserves. That's what I say. Give, give people the stew that they deserve.
SARAH: That's actually perfect advice. Give people the stew they deserve. I'm going to write that down. Thank you, Luke. I needed someone to tell me I was being ridiculous. about the enchiladas.
LUKE: Well, here I am to tell you you were being ridiculous about the enchiladas. And that, ladies and gentlemen, is the end of our 20th episode. Thank you for tuning in. We hope you're enjoying Luke at the Roost, and we'll catch it again tomorrow.
@@ -0,0 +1,333 @@
LUKE: All right. Hello everybody. Welcome back to Luke at the Roost. I am your host, Luke, and today is Tuesday, February 24th. This is the call-in radio show where you can call in and ask me anything about what's on your mind. I'll give you the best advice I can, though I am not a lawyer, I am not an accountant, and I give terrible advice. So with that being said, if you'd like to call in, the number's 208, 4th, 3,000, 3rd, 39-5853. That's 208-439. Luke. First up on the phones here, we've got Greg. Greg, welcome to the show. How can we help you today?
GREG: Luke, hey, yeah. So our smart speaker tried to wake up the entire neighborhood at four in the morning, blasting mariachi music. And I'm pretty sure my wife's never going to let me hear the end of it. The thing is I never even wanted the damn thing. My daughter set it up last Christmas, said it'd make our lives easier, and I've been side-eyeing it ever since. Just sitting there on the kitchen counter like some kind of little robot spy. And this morning it just decides, 4 a.m. Time for a fiesta, apparently.
LUKE: Well, what else do you use it for? There's some pretty good uses for it. It is a little robot spy sitting there listening to everything you say, but it can be fun too and useful.
GREG: Honestly, Linda uses it for timers when she's cooking. asks it about the weather sometimes. I mostly just walk past it. She'll say, play some Fleetwood Mac, and it does, and I guess that's nice enough. But I work nights, Luke, so when I'm home during the day, I'm trying to sleep. And more than once, that thing has started talking out of nowhere because it thought it heard something.
LUKE: Well, you know, you can turn the microphone off, so it doesn't operate that way?
GREG: Yeah. My daughter told me that too. But then Linda says, well, what's the point of having it if we turn it off? And she's got a point, I guess. makes me uneasy. I grew up with a rotary phone on the kitchen wall, you know. You wanted music you put on a record.
LUKE: Yeah, well, now it's 2026 and that's not how it's done anymore. And there are some ways that you can manage that device without having the speaker on. I mean, you have to have the speaker on something, like a smart watch or something. But you can set up a shortcut, so if you talk to your watch, it'll play Fleetwood Mac on the smart speaker.
GREG: I don't have a smart watch, Luke. I've got a where I work about eight years ago, still keeps perfect time. But look, the real kicker this morning was I had to climb up into the attic in my boxes to pull the plug because the thing wouldn't respond to anything we said.
LUKE: Okay, well here's my advice for you. This is what I would do. I would set up a shortcut. You might not have a smart watch, but you do have a smartphone, so you can open up the Google Home or Apple HomeKit or whatever it is app for your smart speaker and set a schedule so that at 10 p.m. it, uh, you can open up the uh, mutes the volume until 8 a.m. And then you don't have to worry about waking up the neighbors at 4 a.m.
GREG: Huh. That's actually not a bad idea. I didn't even know you could do that. See, this is the problem. My daughter sets this stuff up and then moves back to Phoenix, and I'm just supposed to figure it out. Linda standing there in her robe this morning with a flashlight, laughing so hard she's crying while I'm freezing my ass off in the attic. And I'm thinking there's got to be a better way to handle this.
LUKE: Yeah, you're supposed to figure it out. You know how you figure it out? You fucking try. It's really not that hard. It's not rocket science. You actually are like, hey, how could I solve this problem? And you don't even have to know the answer. You just go to your search engine of choice. Google is one good one, and you can say, how can I stop this smart speaker from going off in the middle of the... Just fucking try. That's all you have to do.
GREG: All right, all right. I hear you. You're not wrong. I guess I've been treating it like it's some kind of alien technology, when really I just need to spend 10 minutes poking around in the app. It's just, I don't know, Luke. When you've been doing the same job for 12 years, stocking the same shelves every night, there's a part of you that stops trying to learn new things. You get in a groove and you stay there.
LUKE: Well, if you want to stay there, then you can look like an idiot every time you start playing fiesta music at 4 a.m. But I would recommend that you don't look like an idiot and you actually put in some level of effort, because these are designed so that a 12-year-old can use them. So if a 12-year-old can use it, and you're a full-grown adult, I'm pretty sure you can figure it out.
GREG: You know what? You're right. I'm sitting here complaining about a problem that probably takes less time to fix than it took me to climb into that attic this morning. I'll pull up the app on my break. Figure out the schedule thing you mentioned. Can't be harder than learning the new inventory system they made a switch to last year. And I managed that eventually.
LUKE: I have full faith in your ability to figure out your smart speaker, sir. I wish you the best of luck, but also stop being a lazy bastard. Estelle, thanks for calling in. What's going on today?
ESTELLE: Hey, Luke. Thanks for taking my call. So I work at a laundromat, and I've been tracking who returns their shopping carts in the parking lot, versus who just leaves them wherever. The numbers are driving me crazy, because I think it actually tells you everything you need to know about a person.
LUKE: Well, I don't know about that, but I'm curious about your inventory tracking system. and their laundry. So you can see that the people that do two blankets a week, they're the ones more likely to return their shopping carts. What's your system look like? How detailed is it?
ESTELLE: No, no, it's not that detailed. I've got the security monitor at my desk in the back office, and I can see the Albertsons across the street. People walk over there, do their shopping, and then I watch what they do with the cart when they get back to their car. I started keeping a tally sheet two weeks ago just marks on paper. One column for people who return it. One column for people who don't.
LUKE: Which column do you yourself belong in?
ESTELLE: I return mine every time. Even if I'm parked right next to the store, and the cart return is on the other side of the lot. My husband thinks I'm being ridiculous about this whole thing, but I can't help it.
LUKE: Now doesn't the store hire? a guy or lady to go collect those carts and move them out of the parking lot?
ESTELLE: Yeah, they do. But that's not the point. The point is, there's no punishment for leaving your cart in the fire lane or blocking a handicapped spot. Nobody's watching. Nobody's making you do the right thing. So when someone walks that cart all the way back to the corral when they could just leave it, that tells me something. That tells me they probably stop if they saw you broken down down on the side of the road. The people who leave it three feet from their car.
LUKE: Well, I think that's making quite a few assumptions about the values of people at large.
ESTELLE: Is it, though? Because I watched someone last week, leave their cart, right in the middle of an empty parking space. And then two hours later, that same person came back in. Because the dryers ate their quarters.
LUKE: And you know what?
ESTELLE: Do you think the dryers ate their quarters because of a karmic retribution? No, but they stood there at my counter, demanding I give them their money back, saying it wasn't their fault, saying I should have signs posted, even though there are signs posted. They wanted me to fix their problem immediately, but they couldn't be bothered to walk 15 feet to put a cart away. I'm just saying there's a pattern. Tonight, I counted 47 people who returned their carts versus 63 who didn't. And yeah, maybe I'm reading too much into it.
LUKE: But when you sit here night after night, watching people make that choice, when nobody's looking, it starts to feel like it means something. Well, there's a difference here. You see, the difference is that person was not an employee of Albertsons, and it's not their job to return the cart to the designated cart return space. Whereas in the laundromat, it is your job to fix the problem. And if that person lost their quarters in the machine, then yes, they should expect that you're going to fix their problem. That's your entire job.
ESTELLE: Okay, but the dryer didn't eat their quarters. They put in six quarters for a 40 minute cycle and pulled their clothes out after 20 minutes because they were in a hurry. Then they came back two hours later saying the machine stole from them. I showed them on the timer that they stopped it themselves.
LUKE: Okay. Well, that's different. That is douchebaggery, and I can agree with you on that. And it's a good thing that you had that, uh, that tape ready to show them that, hey, this machine did not eat your quarter, sir. And by the way, you left your shopping cart in the middle of the shopping space and it made me very upset.
ESTELLE: I didn't say that last part to them. I'm not confronting customers about shopping carts at Albertsons, but I'm watching it happen on my security monitor. Because those carts end up blocking my dumpster, and last month, one of them rolled into a customer's Subaru. And I'm sitting here thinking about how it's the same kind of thing. When there's no consequence, when nobody's watching, what do people do?
LUKE: Well, I can't talk about people at large, but I can tell you what I do. When I have a shopping card and I bring it to my car, I unload my goods, my merchandise into the car. And then if there is a spot where I can deposit, that cart within a reasonable distance, I will deposit it. Otherwise, I'll move it somewhere out of the way because usually a parking lot is completely empty and there's plenty of space to drive around a cart, and I'll allow the person whose job it is to put that away to come put it away.
ESTELLE: See, that's what I'm talking about. You just said out of the way and plenty of space to drive around it. But who decides what's out of the way? Because I've got full footage of people leaving carts in handicapped spots, thinking they're out of the way. And, yeah, the parking lot looks empty at 10 o'clock at night. But then someone pulls in at 6 in the morning when it's still dark and there's a cart sitting there. It takes 30 seconds to walk it back. 30 seconds.
LUKE: Well, maybe in that Albertson, but not in every parking lot. There are some that are split in the middle with a sidewalk type thing where you got to lift up the cart and carry it over and then push it back to the front of the store. No, I'm not doing that. But if it's reasonable and there's a little place to put the carts, I would say about 80% of the time I return the carts. And 20% of the time, no. You return the cart. If it bothers you, get out of your car and move it.
ESTELLE: So you're part of the 63. And that's fine. You can be part of the 63. But don't act surprised when nobody stops to help you change a tire, because I'm telling you, there's a connection. The people who can't be bothered to take 30 seconds when nobody's watching. Those are the same people who stand at my counter demanding I fix a problem they created. And my husband thinks I'm obsessing over this, but I started keeping the tally sheet two weeks ago just to see if I was crazy. And the numbers don't lie.
LUKE: You're right, they don't lie. You are crazy. Because sitting at your job, watching across the street and counting the number of times that people return their shopping cards is bat shit crazy.
ESTELLE: You know what? Fair. Maybe it is. But I'm sitting here anyway waiting for the last dryers to finish staring at this monitor. Because someone has to watch for people dumping trash in my dumpster or trying to break into the change machine. And the Albertson's parking lot is right there in frame. So yeah, I started counting.
LUKE: Well, good for you. How is it improving your life in any way? Instead of, you know, taking your personal pet peeve and associating a moral gauge of people's goodliness based on it.
ESTELLE: Because it's not just a pet peeve. That cart that rolled into the Subaru, I had to deal with that.
LUKE: Yeah, what did you have to do to deal with that?
ESTELLE: The woman came into my laundromat screaming because she saw it happen on my camera and wanted the footage. And I gave it to her. And you know what it showed? Some guy leaving his cart right where I've got the no parking dumpster access sign and then it just rolled rolled right across the lot into her car.
LUKE: Well, that is a goddamn travesty. You know what I suggest? Here's my recommendation for you. Right where it says that no parking dumpster access sign, you should also put another sign that says don't leave your cart here. Uh, because placing a car is not the same as parking a car. Thing is, they move easily, whereas a car you kind of have to turn it out of the way. There's a big difference. So, yeah, I don't think this is a thing.
ESTELLE: Oh, so now I need signs telling people not to leave carts in a fire lane, not to block handicapped spots. How many signs do I need before people just do the basic thing? That's my whole point. It's not about the carts.
LUKE: Okay, well, you're the one that's tracking the carts on a graph there sitting at your desk. So what is it about if it's not about the carts?
ESTELLE: It's about the moral responsibility of society at large. It's about whether someone does the right thing when nobody's making them. That's it. The cart return is right there. You can see it from anywhere in that lot. Takes 30 seconds. But 63 people today couldn't be bothered because there's no consequence.
LUKE: Yeah, there's no consequence because it's not a law. There's no rule or regulation or law that says you have to return your cart to the place. And that's why there's no consequence. If you want consequence, I suggest that you, I don't know, join your local board of selectmen and issue a state or county wide, townwide, whatever. Issue a town bylaw that all carts will be returned to the cart space. But you see, the thing is, this is this is America. And people want people to come to their stores and they don't want to be hassled with the carts. So that's why they hire somebody to go clean up the carts. Now, if the carts aren't being cleaned up at the end of the night when the store is closed and everybody's going home, whose fault is that? Is it the fault of every individual shopper that was trying to get, you know, their kid to stop crying and get home to make dinner for the family? Or is it the kid that, uh, that had that job and didn't do it before he went home?
ESTELLE: The Albertson's kid isn't out there at 11.30 at night when I'm closing up. And there's three carts blocking my dumpster access. And yeah, maybe that mom had a screaming kid. I get it. But what about the guy I watched tonight in a suit? No kids. Just walked his cart 10 feet past the return to leave it in a handicap spot. What's his excuse?
LUKE: He had a bunion.
ESTELLE: Oh, come on. You don't. Look, I watched him. He was fine.
LUKE: Oh, so you can tell? You can tell if he was sick to his stomach and about to puke, tell if his big toe was cut off by a meat cleaver accident this morning? You can't look at somebody and tell how they feel or what's going on in their body or in their mind. I'm sorry, lady. You're out of your mind.
ESTELLE: Okay, fine. Maybe that one guy had a bunion. What about the other 62 people? They all have bunions?
LUKE: I'm going to guess that some percentage of them are general assholes, but the vast majority just had other stuff going on. And this is not their primary, you know, priority today.
ESTELLE: But that's exactly what I'm saying. When it's inconvenient, when they've got other stuff going on, they don't do it. That's the test. Anyone can do the right thing when it's easy.
LUKE: I'm talking about the 30 seconds when you're tired or distracted or your toe hurts. Do you still put the cart back?
ESTELLE: I put the cart back when it makes sense to put the cart back. And when it doesn't, I don't put the car back. And I'm an adult with agency and the ability to tell when it's okay to leave a cart somewhere and when you're blocking something important. And I think most adult people that are able to drive can make that distinction. And it's really not up to you. It's not your job. And it's not your responsibility to decide when that's okay or where it's okay.
LUKE: But you just said you can tell when you're blocking something important. The guy tonight left his cart in a handicapped spot. That's blocking something important.
ESTELLE: Sure, and I told you that there was going to be some percentage of assholes. But that doesn't mean that guy didn't stop on his way home and help an old man fix his tire. It doesn't mean he wouldn't go out of his way to support a food kitchen on Christmas because you don't like the way he handled his cart. Come on.
LUKE: So what? One good deed cancels out being inconsiderate. I'm supposed to assume the guy blocking the handicapped spot is secretly Mother Teresa, because maybe he volunteers sometimes.
ESTELLE: Look, I'm not saying these people are evil.
LUKE: You are. That's exactly what you're saying. You're saying you can tell from whether or not people return their carts how they are in general towards society. You're saying the people that don't return their carts aren't helpful and they're not going to stop on the side of the road and you're making a ton of assumptions here and you're not in a position to do that lady.
ESTELLE: I didn't say they're evil. I said you can tell who would help you change attire. And yeah, I stand by that. The person who can't be bothered to walk 15 feet when they're done shopping, when nobody's watching. Well, I mean, I don't know.
LUKE: What are you calling for? What advice do you need for me?
ESTELLE: I don't know. My husband says, I'm obsessing. He says I need to let it go. Stop keeping the tally sheet. Stop watching the monitor. But I can't stop noticing it now.
LUKE: Well, you obviously are obsessing, and maybe you should let it go because it's obviously not improving your life in any way. And it's making you a miserable old bag.
ESTELLE: Okay, wow. Miserable old bag. That's nice. All right. So I guess I'm one of those 40% centers or whatever that sucks of life. But I wish you the best and I hope that all your carts end up where they're supposed to go, okay?
LUKE: With that being said, it's time for a word from 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 home coffee Enema 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 your, that that coffee needs to be single origin, a detailed instruction booklet and a com card with a QR code to a guided meditation for during. During. There's a guided meditation for during. 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 we are back. And it is time for a, uh, A listener mail. So here's a submission that we've got to the old email box. If you'd like to send in a submission, it is submissions at luke at the roost.com. And let's see what we've got here. I've been lying to a dying man for seven months and I can't stop. Let's have one of our robot friends read this email.
Subject, I have been lying to a dying man for seven months and I can't stop. Luke, I worked the graveyard shift at a truck stop diner. Seven months ago, this old guy started coming in every single night. Same time, same booth, same order. Black coffee, dry toast, side of applesauce. One night he starts talking to me. Tells me his name is Earl. Tells me he's got the cancer. Tells me his daughter won't return his calls. Then he asks my name. And I don't know why, Luke, but I panicked and I said Diane. My name is not Diane. Not even close. But he got this look on his face like he'd just seen a ghost He whispered, that was my mother's name. And then he cried, and I couldn't take it back after that. So now I'm Diane. Every night. For seven months, I have been Diane to this dying man. I know everything about him. I know he drove trucks for 40 years. I know he regrets missing his daughter's wedding. I know his wife's name was Ruthie, and she made the best cobbler in the state, and he still sets a place for her at Christmas. My coworkers think I'm insane. They call me Diane now too, even when Earl's not there. My manager put Diane on the schedule as a joke. It's not funny anymore. It's on my name tag. I am Diane now. But here's the thing. Earl told me last week that coming in and talking to Diane is the only reason he gets out of bed. That our conversations are keeping him going. That I remind him there are still good people. What am I supposed to do with that? Tell a dying man that the one good thing in his life is based on a lie I told because I got nervous? I don't need advice. I think I'm past advice. I just needed someone else to know that somewhere out there, a woman who is not Diane, is the most important person in a dying man's life. And she has no idea how this ends. Keep the light on for us night shift people. We're out here holding it together with coffee and fake names. Not Diane.
All right. Well, thank you, Not Diane. That's a very kind thing of you to do. If you'd like to send in a message, you can do that at submissions, Luke at theroost.com. Now let's get back to the phones here. We've got Francine. Francine, welcome to the show. What's going on?
FRANKIE: It's Frankie, actually. But yeah, hey, Luke. So I'm sitting here looking at my buddy Ray's Netflix account, right? And the guy's got like 15 romantic comedies in his cue and a profile that says watching with them with a little heart emoji.
LUKE: Okay. Well, uh, what's the problem there?
FRANKIE: The problem is Ray's been giving me endless shit for years about being with my girlfriend Amber. Like constantly. The whole ball and chain routine. The, you're missing out on freedom speech. Jokes about how he's never settling down. All that stuff. And now I find out he's been in a relationship this whole time and just never said anything?
LUKE: Well, how do you know he's in a relationship? He could just be, uh, you know, that could be a strategy for his Netflix and chill routine.
FRANKIE: I mean, maybe. Maybe? But who makes a whole profile with a heart emoji for hookups? And these aren't just rom-coms, Luke. We're talking the notebook. When Harry met Sally, the really sappy ones. Plus, I was on his account like a month ago watching Narcos. And that profile definitely wasn't there.
LUKE: Well, I don't know what to tell you, buddy. It sounds like your friend might be into some... Uh... shit. I don't know how to say it. Less than masculine media.
FRANKIE: No, see, that's the thing, though. Ray doesn't care about that stuff. He'll watch whatever. We've watched plenty of dumb movies together. It's not about the rom-coms. It's about the lying. Like, why would he hide having a girlfriend while busting my balls about having one? That's when I can't figure out.
LUKE: Well, you don't know. Maybe he doesn't have a girlfriend. Maybe the girlfriend is somebody you know and he doesn't want you to know.
LUKE: But also, a possibility is he's let somebody else use his account, and they're setting up a playlist for their girlfriend.
CALLER: Oh, man, I didn't even think about that. Like maybe his brother or something? But wait. No, raise an only child. And who would he be dating that I know? We run in the same circles pretty much. Unless... I don't know, man.
LUKE: How should I know? It doesn't matter. Why are you focused on this?
CALLER: Talk to Ray and ask him if he's got a girlfriend. Because I've been sitting here for an hour trying to figure out if bringing it up makes me look like a nosy asshole. That's why. Like, what do I even say? Hey, man, I was snooping through your Netflix and noticed you might have a secret girlfriend? That sounds insane. But also, he's my best friend. And he's apparently been hiding something major while making me feel like an idiot for being in a relationship.
LUKE: Yeah, that's what friends do. So rag on him a little bit. You know, give me me. some shit about the notebook or whatever the next time you see him, but it's not a big deal.
CALLER: I don't know if this is something worth calling a radio show over.
LUKE: You're right, you're right. I know you're right.
CALLER: It's just been eating at me because Amber keeps asking why I'm being weird tonight, and I can't exactly tell her I'm spiraling over Ray's Netflix account.
LUKE: Yeah, man, this is not a rational behavior on your part. So I'm going to recommend that you leave it alone for a while, watch something good on Netflix, and thank your buddy for letting you use his account.
CALLER: Yeah, okay. Fair enough. I'll just drop it. Maybe send him a text tomorrow like nothing happened.
LUKE: Good plan, sir. Nothing did happen. You fucking weirdo. Okay, next up we've got Shanice.
SHANICE: Shanise, what's going on in your life? Hey, Luke. It's Shanice, actually.
LUKE: Yeah, that's what I said.
SHANICE: So I just found pictures of my uncle Raymond, at Burning Man, wearing nothing but a leather harness. And he's painted silver and orange head to toe. And I'm trying to figure out, if I should show them to the guys at our poker game Friday, because this man lectures me about living right every single Sunday at church.
LUKE: Well, you know what? If he's naked a burning man in a leather harness and he's painted silver and orange, it sounds to me like this man's living right.
SHANICE: You know what? That's the thing that's messing with me right now. Like part of me is sitting here looking at these photos, and I'm kind of impressed. He's 70, three years old, and he looks happier in these pictures than I've ever seen him. But then every Sunday he's. He's sitting there telling me, I need to get back out there and date. I need to join the church choir. I need to stop spending so much time in my workshop mounting dead animals. Meanwhile, he's out in Nevada doing whatever the hell this is.
LUKE: He's partying. He's enjoying his golden years. Good for him. I don't think he can make fun of him for that. That's pretty badass.
SHANICE: Okay, but Luke, you don't understand. This is the same man who gave me a 30-minute lecture last month, because I had a beer at two in the afternoon. afternoon on a Saturday. He saw the can in my truck and went on about responsibility and setting an example. And now I'm looking at him in light up goggles, dancing in the desert in his underwear. And I'm supposed to just let him keep acting like he's the moral authority.
LUKE: Yeah, you are, because there's no crime being done there if he's partying out in the desert at the appropriate place to do that. That's what it's for. He went out there to be a little hedonistic in the right people and he paid money to do it. You on the other hand have a container in a vehicle which is a crime and you probably shouldn't be driving and drinking at the same time.
SHANICE: Okay, first of all, the truck was parked in my own driveway and the engine wasn't even on, but fine, you got a point. I hear you.
LUKE: So you're just sitting in the driveway, drinking beers, throwing them in your car when you're done?
SHANICE: Well, when you say it like that, it sounds worse than it is. I was cleaning out my workshop and I sat down for a But okay, whatever. That's not the point. The point is, he found those pictures and felt the need to give me a whole speech about it. And now I'm holding evidence that he's got this entire secret life. And Friday night I got four guys coming over who have known Raymond since before my husband died, and they worship this man. They think he's this pillar of the community.
LUKE: You might be, you can be more than one thing. And I don't know that a weak at Burning Man is the same thing as, uh, uh, uh, an entire secret life.
SHANICE: You're right. Secret life is dramatic. But Luke, these guys at poker, they ask him for advice. Bobby asked him last month if he should propose to his girlfriend, and Raymond told him to pray on it and make sure he was ready for that level of commitment. And I'm just sitting here thinking, what would Bobby say if he knew Raymond was out there in a harness? I'm not saying I'm going to show them. I'm just saying the temptation is real, because he acts like he's got it all figured out. The rest of us are just stumbling around making mistakes.
LUKE: What do you think God doesn't like leather? Do you think there's no church or, uh, spiritual people at Burning Man? I'm, I'm pretty sure you're mistaken.
SHANICE: Okay, that's fair. I didn't think about it like that. I guess I just assumed. Yeah, you're right. That's on me. But here's what's really getting to me now that I'm talking it through with you. It's not even about the Burning Man thing anymore. It's that he sees me struggling, right?
LUKE: Yeah, okay, so he sees you struggling, and it sounds like he's giving you reasonable advice.
SHANICE: He is, but he's giving it from this place like he's never struggled himself, like he's never done anything messy or confusing or outside the lines. And now I know he has, and he just never told me. When my husband died, Raymond was there every single day, telling me how to grieve the right way. What I should be doing? When I should be moving on. And maybe if I'd known he had this whole other side to him, that he wasn't just this rigid person. Maybe I would have felt less like I was failing at everything.
LUKE: Well, you know, I'm sure he was trying to give you the best advice he could and help you out. And not everybody's good at doing that, and they try to impose their own values and will on you and their own experiences. But you don't know how somebody else has struggled or what they've done that's messy or confusing. There's a good chance that he's done a lot of messy confusing things. And he's trying to do. his best to give you the type of advice that leads you in a different direction.
SHANICE: You know what? You're completely right. I'm sitting here acting like I know everything about him because I found some photos, but I don't know what led him there or what he was going through when he went. Maybe he was struggling with something huge and that's how he dealt with it. And here I am ready to embarrass him in front of his friends just because I'm still mad about how he handled things seven years ago.
LUKE: You know, I've never been to Burning Man, but I've watched a lot of reports from people that enjoy that and go there. I think I would hate it personally because of the crowds and the drugs and partying. It's not really my thing. But most of the reports I've heard of people from from Burning Man and Burning Man like events are wildly positive. They say they're, you know, some of the best most spiritual experiences of their lives. And the whole of Burning Man is pretty great if you get down and look into the anti-capitalism and art and free love and all that jazz. I don't see how you can look at that with such negativity. He went out to the desert to have a good time and had a good time. And that's all there is to it.
SHANICE: Yeah, I hear you. And honestly, looking at those pictures again, he looked happy, really happy, like genuinely having the time of his life.
LUKE: And when's the last time I saw him look like that here. So yeah, you should talk to Raymond and maybe ask him if you can go with him this year and see what it's about and have a more informed viewpoint of what's going on there before you start trying to destroy the guy's life and reputation for it.
PATTY: Oh man. Can you imagine me at Burning Man? I'd be the person bringing a cooler and asking where the bathrooms are. But you're right, Luke. I called in here ready to justify being Patty. And you just made me realize I'm jealous. I'm jealous that he went out and something wild and I'm still here in the same workshop mounting the same animals for the same clients.
LUKE: Well, get out there and do something wild and maybe enjoy your life a little bit more and don't worry so much about what Raymond says. You know, you know what you've got to do in your life. You don't have to take his advice. Maybe he'll give it when it's unsolicited and you can just ignore it. But it sounds like he's a decent man that is speaking to you with the best of intentions.
PATTY: You're absolutely right. I'm going to keep my mouth shut at poker. And maybe next time he starts lecturing me about living right, I'll just smile and let it roll off. He's trying to help in his own way, even if it drives me crazy sometimes. And honestly, finding those photos was probably the best thing that could have happened. Because now when he gets on his high horse, I can just picture him in that harness and light up goggles. And it'll take the sting out of it.
LUKE: Excellent. See, that's what we do here. Look at the Roots. We bring families back together. Uh, thank you for the call. I hope everything goes well with your uncle. And now it's time for a word from our sponsors. This episode is brought to you by Desert Gut, the all-in-one nutritional supplement made from things you'd actually find within walking distance from my RV. Each scoop of Desert Gut contains 17 adaptogens, nine minerals scraped directly off a rock, pulverized tumbleweed fiber, and a proprietary blend we're calling coyote dust. which our lawyers have asked me to clarify as not made from actual coyotes. Desert gut tastes like someone described the color beige to a blender, but you'll feel incredible, or you'll feel something. First five callers get a free shaker bottle that definitely used to be a gas station coffee cup. Desert gut, because your gut isn't going to desert itself. All right. to the show. And now on the phones we've got T-Bone. Teabone, welcome to the show.
T-BONE: Luke, hey, yeah, I'm calling about my sister's gender reveal party tonight. And I got to tell you, this thing got completely out of hand. I'm talking a D, J, a catered taco bar, 200 people in my backyard. And at the end of it all, she shoots off this confetti cannon that covers my entire property and pink glitter.
LUKE: What did you expect was going to happen at a gender reveal party?
T-BONE: Well, I expected maybe. be, I don't know, a cake, some balloons, not a full production with the sound system, and a guy in a bowtie handing out shrimp cocktails. I've got five acres out in the desert, Luke, and she turned my property into some kind of festival grounds.
LUKE: Well, I mean, it's a gender reveal party. I think the people that are interested in a gender reveal party are maybe a little bit on the dramatic end of things, and they think their news there is very, very important. So you can understand why maybe they acted that. way.
T-BONE: Okay, but here's the thing. I teach middle school, Luke. I deal with drama all bay long, 13-year-olds crying because somebody unfollowed them on Instagram. And I come home to my sister doing basically the same thing, except she's 34 years old and spent what had to be two grand announcing whether her kids got a penis or not. I mean, my coworker Deshaun said it to me straight at lunch yesterday. Nobody cares this much about baby gender.
LUKE: Wouldn't it be funny if that child grew up to have a different gender identification?
T-BONE: I mean, yeah, that's the other thing. She's over here acting like this pink confetti is some kind of binding contract with the universe. Like, what if the kid decides differently down the road? Then what? She going to shoot off another cannon in 15 years? Look, I don't care what the kid ends up being. I just think the whole spectacle is ridiculous.
LUKE: I agree with you. That is a ridiculous spectacle, and it's completely unnecessary. but, you know, you agreed, and you knew what a gender-reveal party was, so I guess you've got to clean up some of that confetti. But luckily you live in the desert, so it's all going to blow away probably by tomorrow.
T-BONE: Well, that's the thing, Luke. It's not blowing away. It's this heavy-duty stuff, like industrial glitter mixed with paper. And my chickens are out there eating it right now. I raise heritage breed Wyandots. And I got no idea if this stuff is going to mess with their digestive system.
LUKE: Nothing messes with their digestive system. system. Worst case scenario, you're going to have pre-decorated Easter eggs.
T-BONE: Huh. Yeah. Pink-glitter chicken eggs. That'll go over real well at the farmer's market. But seriously, I didn't even want to say yes to this thing. She asked me two months ago, and I should have just told her no. Use your own yard. But she lives in one of those H-O.
LUKE: What's an H-O?
T-BONE: H-O-A, Homeowners Association. She's in one of those cookie-cutters' subdivisions where you need written permission to change your mailbox color. So she can't do anything fun at her place. And I'm out here with five acres, and apparently that makes me the family event venue now. I got a Silverado in the driveway, some land. And suddenly, every birthday, every baby thing, it's, can we use your place, T-bone? And I keep saying yes because she's my sister. But man, I'm getting tired of being the backup plan for people who chose to live in places with rules.
LUKE: Well, you're going to have to stand up for yourself and say, that you made a mess out of my place the last time and I don't want to pick it up. So next time just say, no, we're going to do this at your place. Or you're going to have to reel in your party so that it fits within the allotted rules of your HOA.
T-BONE: You're right. You're absolutely right. I just, I don't know why I can't say it to her face. I can tell 13-year-olds all day long that their essays late, and I'm not accepting it. No exceptions. I can stand in front of 30 kids and lay down the law about cell phones. But my My sister asked me for something, and I just fold.
LUKE: Yeah, well, it's pretty easy to tell a kid what to do. You have an interesting power dynamic there, but with family, it's a little bit different. Still, if you want to be a man, then stand up for yourself and say what you want, what you will accept, and what you will not accept.
T-BONE: Yeah, you're calling me out, and I hear you. I make decent money teaching. I owe my property outright. I bench 225. I shouldn't be acting like I can't set a boundary with my own sister. It's just she's going through a lot right now, you know? Her husband works offshore two weeks at a time. She's about to have this baby. And I think part of me feels like I should just suck it up and help out.
LUKE: Well, you've got to pick one or the other. You can suck it up and help out and do whatever it is that she wants and then clean up her industrial-grade glitter from your chicken's assholes. Or you can say, no, I don't want to do that anymore. You're going to have to find another place. But that's completely up to you. And you're going to be. going to have to make that choice based on how you want to live your life.
T-BONE: You know what? You're right. I'm sitting here complaining to you at midnight about glitter, when I could have just said no two months ago. Her husband makes good money on those rigs. Way more than I make teaching. They can afford to rent a pavilion at a park, or do this thing at a restaurant like normal people. I don't need to be the solution every time, just because I've got the space.
LUKE: Next time she asks, I'm telling her straight up. Find somewhere else. There you go. That's standing up for yourself. That's what I always say. Stand up for yourself. Do the right thing. And then you don't have to clean up glitter. We're going to take one more call tonight because it's getting a little bit late. And the last caller here we've got on the line is Mavis. Mavis, welcome to the show. You're the last caller tonight. What would you like to talk about?
MAVIS: My brother owes me $8,000, and I'm about to take his dogs.
LUKE: Wait, what? You're going to steal his dogs?
MAVIS: Not steal. Collateral. He borrowed $8,000 to fix his roof back in October. Said he'd pay me back by Christmas. Well, it's almost March, and I came home today to find my living room trashed because he's been using my place as a kennel while I was working in Lordsburg all week. Carpets destroyed. Tea. Vs cracked and Ed when I called him. Actually laughed and said shit happens. So yeah, I'm taking two of his pointers.
LUKE: Do you even like dogs?
MAVIS: Like them. Luke, I trained those dogs. That's my bloodline going back to a bitch I bought in 98. I've been breeding and training pointers for 26 years.
LUKE: Well, it sounds like you didn't do a very good job if they destroyed your house now, did you?
MAVIS: Those dogs are perfectly trained when they're handled right. My brother's lazy. He doesn't exercise them. Doesn't give them structure, just dumps them wherever it's convenient. A pointer needs work. They need routine.
LUKE: Well, I mean, I hope that works out for you. I don't think you can just take somebody's dogs. I mean, I would never let anybody take my dog. So if he's going to just allow you to take his dogs, I don't know what says about him.
MAVIS: He's not going to allow it. I'm just going to do it. I've got keys to his place and I know his schedule. He works nights at the mine Thursday through Sunday. I'll go Friday morning, load them up, and he can come get his money together if he wants them back.
LUKE: And what happens if he calls the police on you?
MAVIS: And tell them what? That his sister took back the dog she bred and trained because he owes her $8,000 and destroyed her house? I've got the vet records. I've got the category papers with my name on them. I've got text messages about the loan. He's not calling anybody.
LUKE: I don't think it works that way. If they're his dogs, you can't just take them, even if you have the training papers. I mean, that'd be like any trainer that trained a dog coming and just taking the dog back. It doesn't work that way. And people have a severe amount of sympathy for dogs and pet owners. So if somebody's missing their dogs, you're not going to have a public opinion on your side here.
MAVIS: Public opinion. Luke, this isn't about public opinion. This is about my brother thinking he can walk all over me because I'm always the one who bails him out. I loaned him money for his roof. I trained his dogs for free. I let him use my house. And every single time he takes advantage. The dogs are leverage. He gets them back when I get my money back.
LUKE: Okay, well, I hope that works out for you. You sound like somebody I wouldn't want to fuck with.
MAVIS: You're damn right. I've been too nice for too long, and look where it got me. Three grand in repairs and a brother who laughs about it.
LUKE: Well, you know what could have? I mean, you could take those dogs and then find out one of them has cancer, and now you're on the hook to pay for this dog that's not yours. Chemotherapy.
MAVIS: Then he can come get his sick dog and pay me what he owes. I'm not keeping them forever, Luke. I'm holding them until he pays up.
LUKE: What if he doesn't pay up?
MAVIS: Then I've got two excellent bird dogs that I train myself, and he's got a debt he'll never forget. Either way, I'm done being the family dormant. He'll pay. He loves those dogs more than he loves most people.
LUKE: All right, then I wish you luck, and I wish those things. Dogs look, it seems a little fucked up that you're making those dogs collateral on a loan to your own family. That's a bit... This whole scenario seems a little bit excessive.
MAVIS: Excessive? He destroyed my living room, Luke. My carpets ruined. My TV's cracked. My favorite movies snapped in half. I came home after a week taking care of a stroke patient, and my house looked like a frat party happened in it. And when I called him Ed?
LUKE: Why'd you call him Ed?
MAVIS: Oh, that's his name. Ed. Edward. But nobody calls him that. He's always been Ed to me.
LUKE: All right. Well, good luck to Ed. Jesus Christ. You sound like Satan.
MAVIS: Satan would have taken the dog six months ago when he brought my truck back on empty. I gave him a pass on that one. Not this time.
LUKE: All right. Well, it sounds like you've got it all figured out. So I wish you the best and I wish those dogs the best. And fuck, I wish Ed the best.
MAVIS: You think I'm being too harsh.
LUKE: Slightly. Luke. I told him what happened and he said shit happens and laughed. Like it was nothing. Like I'm supposed to just eat $8,000 in a destroyed living room because we share DNA.
MAVIS: Hey, you gotta do what you gotta do. So go do it. And I wish everybody the best in this whole scenario.
LUKE: I'm glad that you're not mad at me.
MAVIS: I'm not mad at you. I'm mad at him. And I'm mad at myself for letting it get this far.
LUKE: Okay. Well, thanks for the call. And I hope you get a good night's sleep and maybe think about this overnight. time and see if it's what you really want to do, all right?
MAVIS: Yeah. Yeah. All right. Thanks for listening, Luke.
LUKE: Wow. Jesus. I am glad that I haven't pissed off Mavis. Wow. All right. Well, that's the end of our show, folks. Thanks for tuning in again, and we'll talk to you tomorrow. Bye-bye.

Some files were not shown because too many files have changed in this diff Show More