aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-26 18:23:46 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-26 18:23:46 -0700
commitc8a588062f31b00b8c302a3e9f5fff6b43110dc2 (patch)
tree95c83ae6762334d32bcea5217bbd827fb645cb52
parentee3b563ecec4a6e9e7755d3ed6bc08faa3916545 (diff)
podcast: switch to server-side php proxy for rss feed
itunes api unreachable from browser on vps. podcast.php fetches anchor.fm rss server-to-server, parses xml, returns json. 1h cache header to avoid hammering the feed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--aero.js23
-rw-r--r--podcast.php69
2 files changed, 72 insertions, 20 deletions
diff --git a/aero.js b/aero.js
index 01a9b83..98cb8ec 100644
--- a/aero.js
+++ b/aero.js
@@ -177,26 +177,9 @@ async function fetchVisitorCount() {
}
async function fetchReelMouthFeed(limit = 6) {
- const url = `https://itunes.apple.com/lookup?id=1709836497&entity=podcastEpisode&limit=${limit + 1}`;
- const r = await fetch(url);
- if (!r.ok) throw new Error('itunes ' + r.status);
- const j = await r.json();
- const podcast = j.results.find(x => x.kind === 'podcast');
- const episodes = j.results.filter(x => x.kind === 'podcast-episode').slice(0, limit);
- return {
- art: podcast ? podcast.artworkUrl600 : null,
- episodes: episodes.map(e => {
- const ms = e.trackTimeMillis || 0;
- const mins = Math.floor(ms / 60000);
- const h = Math.floor(mins / 60);
- const m = mins % 60;
- return {
- title: e.trackName,
- url: e.trackViewUrl,
- duration: h ? `${h}:${m.toString().padStart(2, '0')}` : `${m}m`,
- };
- }),
- };
+ const r = await fetch(`/podcast.php?limit=${limit}`);
+ if (!r.ok) throw new Error('podcast.php ' + r.status);
+ return await r.json();
}
window.Aero = { spawnBubbles, makeClouds, sparkleCursor, makeDraggable, counterHTML, nowPlayingHTML, animateEq, musicToggleHTML, bindMusicToggle, fetchLastFm, fetchFilms, fetchVisitorCount, fetchReelMouthFeed };
diff --git a/podcast.php b/podcast.php
new file mode 100644
index 0000000..2447fbf
--- /dev/null
+++ b/podcast.php
@@ -0,0 +1,69 @@
+<?php
+header('Content-Type: application/json');
+header('Cache-Control: public, max-age=3600');
+header('Access-Control-Allow-Origin: *');
+
+$rss_url = 'https://anchor.fm/s/e8438774/podcast/rss';
+$limit = max(1, min(20, intval($_GET['limit'] ?? 6)));
+
+$ctx = stream_context_create(['http' => [
+ 'timeout' => 8,
+ 'user_agent' => 'Mozilla/5.0 (compatible; podcast-proxy/1.0)',
+]]);
+
+$xml = @file_get_contents($rss_url, false, $ctx);
+if ($xml === false) {
+ http_response_code(502);
+ echo json_encode(['error' => 'could not fetch feed']);
+ exit;
+}
+
+$feed = @simplexml_load_string($xml);
+if (!$feed) {
+ http_response_code(502);
+ echo json_encode(['error' => 'could not parse feed']);
+ exit;
+}
+
+$ns_itunes = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
+
+// artwork from channel-level itunes:image
+$channel = $feed->channel;
+$itunes_ch = $channel->children($ns_itunes);
+$art = (string)($itunes_ch->image->attributes()['href'] ?? '');
+
+$episodes = [];
+foreach ($channel->item as $item) {
+ if (count($episodes) >= $limit) break;
+ $itunes = $item->children($ns_itunes);
+ $duration_raw = (string)($itunes->duration ?? '');
+
+ // normalize duration to h:mm or Xm
+ if (strpos($duration_raw, ':') !== false) {
+ $parts = array_map('intval', explode(':', $duration_raw));
+ if (count($parts) === 3) {
+ [$h, $m, $s] = $parts;
+ $duration = $h > 0 ? "{$h}:" . str_pad($m, 2, '0', STR_PAD_LEFT) : "{$m}m";
+ } else {
+ [$m, $s] = $parts;
+ $duration = "{$m}m";
+ }
+ } elseif (is_numeric($duration_raw)) {
+ $secs = intval($duration_raw);
+ $h = intdiv($secs, 3600);
+ $m = intdiv($secs % 3600, 60);
+ $duration = $h > 0 ? "{$h}:" . str_pad($m, 2, '0', STR_PAD_LEFT) : "{$m}m";
+ } else {
+ $duration = '';
+ }
+
+ $link = (string)($item->link ?? $item->enclosure->attributes()['url'] ?? '#');
+
+ $episodes[] = [
+ 'title' => (string)$item->title,
+ 'url' => $link,
+ 'duration' => $duration,
+ ];
+}
+
+echo json_encode(['art' => $art, 'episodes' => $episodes]);