From a1e17081c5603c207b6ed047474c07ed5819b62b Mon Sep 17 00:00:00 2001 From: mrwnslz Date: Fri, 2 Oct 2026 07:48:45 +0200 Subject: [PATCH] Podcasts: neues Abo bringt nur die neueste Folge in die Inbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim ersten Abruf eines Feeds (Abonnieren, OPML-Import, gpodder) kam der ganze Rückkatalog als „neu“ in die Inbox (bei Lage der Nation 200 Folgen). Jetzt zählt nur die neueste; die älteren sind ausgeblendet, nicht gehört, und stehen weiter in der Folgenliste des Podcasts. Auch automatisches Einreihen nimmt beim ersten Abruf nur diese eine. Co-Authored-By: Claude Opus 5.5 --- podcasts/tests.py | 34 ++++++++++++++++++++++++++++++++++ podcasts/views.py | 14 ++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/podcasts/tests.py b/podcasts/tests.py index 2828a4c..4a0f420 100644 --- a/podcasts/tests.py +++ b/podcasts/tests.py @@ -111,6 +111,7 @@ class AutoQueueTests(TestCase): self.user = User.objects.create_user(username='alice', password='pw12345678') self.feed = PodcastFeed.objects.create( user=self.user, rss_url='https://example.org/feed', title='Feed', auto_queue=True, + last_refreshed_at=datetime(2026, 1, 1, tzinfo=timezone.utc), # not the first fetch ) other = PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/o', title='O') self.old = [ @@ -148,6 +149,39 @@ class AutoQueueTests(TestCase): self.assertEqual(self.refresh(), ['o0', 'o1', 'o2']) +class FirstFetchTests(TestCase): + """A new subscription doesn't flood the inbox with its back catalogue.""" + + def setUp(self): + self.user = User.objects.create_user(username='alice', password='pw12345678') + self.client.force_login(self.user) + self.rss = _rss([(f'e{i}', f'Mon, {i + 1:02d} Jan 2026 10:00:00 GMT') for i in range(5)]) + + def subscribe(self): + with patch('podcasts.views._fetch_feed', lambda url: _parse(self.rss)): + return self.client.post('/podcasts/feeds/add/', json.dumps({'rss_url': 'https://example.org/f'}), + content_type='application/json') + + def test_only_the_newest_is_new(self): + self.assertEqual(self.subscribe().json()['new_episodes'], 5) + inbox = self.client.get('/podcasts/inbox/').json()['episodes'] + self.assertEqual([e['title'] for e in inbox], ['e4']) + # The others are still listed for the podcast, not marked played. + feed = PodcastFeed.objects.get(user=self.user) + eps = self.client.get(f'/podcasts/feeds/{feed.id}/episodes/').json()['episodes'] + self.assertEqual(len(eps), 5) + self.assertFalse(any(e['played'] for e in eps)) + + def test_later_fetches_keep_every_new_episode(self): + self.subscribe() + feed = PodcastFeed.objects.get(user=self.user) + self.rss = _rss([(f'e{i}', f'Mon, {i + 1:02d} Jan 2026 10:00:00 GMT') for i in range(8)]) + with patch('podcasts.views._fetch_feed', lambda url: _parse(self.rss)): + _refresh_feed(feed) + inbox = self.client.get('/podcasts/inbox/').json()['episodes'] + self.assertEqual([e['title'] for e in inbox], ['e7', 'e6', 'e5', 'e4']) + + class RefreshAllTests(TestCase): """One request refreshes all feeds on the server; one request returns the state.""" diff --git a/podcasts/views.py b/podcasts/views.py index 5a89f4d..a651052 100644 --- a/podcasts/views.py +++ b/podcasts/views.py @@ -19,6 +19,7 @@ from .models import EpisodeProgress, PodcastEpisode, PodcastFeed, PodcastQueue, log = logging.getLogger(__name__) FEED_TIMEOUT = 20 # seconds per feed download +NEW_ON_SUBSCRIBE = 1 # episodes of a new subscription that count as new FEED_WORKERS = 8 # feeds downloaded at the same time @@ -167,6 +168,8 @@ def _refresh_feed(feed_obj, parsed=None): if parsed is None: parsed = _fetch_feed(feed_obj.rss_url) channel = parsed.feed + # The first fetch of a new subscription brings the whole back catalogue. + first_fetch = feed_obj.last_refreshed_at is None feed_obj.title = channel.get('title', feed_obj.title or feed_obj.rss_url)[:300] feed_obj.description = channel.get('subtitle', channel.get('description', ''))[:2000] @@ -245,6 +248,17 @@ def _refresh_feed(feed_obj, parsed=None): new_count += 1 new_episodes_list.append(episode_obj) + if first_fetch and len(new_episodes_list) > NEW_ON_SUBSCRIBE: + # Only the newest episodes count as new; the rest stay in the + # podcast's episode list, out of the inbox (dismissed, not played). + newest_first = sorted(new_episodes_list, key=lambda e: e.pub_date or e.discovered_at, reverse=True) + older = newest_first[NEW_ON_SUBSCRIBE:] + EpisodeProgress.objects.bulk_create( + [EpisodeProgress(user=feed_obj.user, episode=e, dismissed=True) for e in older], + ignore_conflicts=True, + ) + new_episodes_list = newest_first[:NEW_ON_SUBSCRIBE] + if feed_obj.auto_queue and new_episodes_list: where = PodcastSettings.for_user(feed_obj.user).auto_queue_position # Newest first in front of what's there; oldest first when appended,