Podcast-Einstellung "notify": über neue Folgen benachrichtigen
PodcastFeed.notify (Standard aus) wie auto_download: in feeds/ und state/, änderbar über feeds/<id>/settings/ (nur true/false). Die Geräte melden damit neue Folgen dieses Podcasts. Der AntennaPod-Import übernimmt Feeds.episode_notification (an -> notify an; aus, AntennaPods Standard, lässt dioras Einstellung stehen; ältere Backups ohne die Spalte: aus). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
262dd0d207
commit
371c9b9222
6 changed files with 66 additions and 7 deletions
|
|
@ -43,7 +43,7 @@ SUBSCRIBED = 0 # Feeds.state
|
||||||
MAX_FILESIZE = 4_000_000_000
|
MAX_FILESIZE = 4_000_000_000
|
||||||
|
|
||||||
SETTING_FIELDS = ('playback_speed', 'skip_intro', 'skip_outro', 'volume', 'include_words',
|
SETTING_FIELDS = ('playback_speed', 'skip_intro', 'skip_outro', 'volume', 'include_words',
|
||||||
'exclude_words', 'min_duration', 'new_episodes', 'auto_download')
|
'exclude_words', 'min_duration', 'new_episodes', 'auto_download', 'notify')
|
||||||
|
|
||||||
# Columns read from the backup, with the value to assume if an older
|
# Columns read from the backup, with the value to assume if an older
|
||||||
# AntennaPod didn't have the column yet.
|
# AntennaPod didn't have the column yet.
|
||||||
|
|
@ -52,7 +52,7 @@ FEED_COLUMNS = {
|
||||||
'image_url': '', 'keep_updated': 1, 'include_filter': '', 'exclude_filter': '',
|
'image_url': '', 'keep_updated': 1, 'include_filter': '', 'exclude_filter': '',
|
||||||
'minimal_duration_filter': -1, 'new_episodes_action': 0, 'feed_playback_speed': -1,
|
'minimal_duration_filter': -1, 'new_episodes_action': 0, 'feed_playback_speed': -1,
|
||||||
'feed_skip_intro': 0, 'feed_skip_ending': 0, 'feed_volume_adaption': 0, 'auto_download': 1,
|
'feed_skip_intro': 0, 'feed_skip_ending': 0, 'feed_volume_adaption': 0, 'auto_download': 1,
|
||||||
'state': SUBSCRIBED,
|
'episode_notification': 0, 'state': SUBSCRIBED,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -191,6 +191,10 @@ def feed_settings(ap, global_auto_download=False):
|
||||||
s['auto_download'] = bool(global_auto_download)
|
s['auto_download'] = bool(global_auto_download)
|
||||||
elif code == AUTO_DOWNLOAD_OFF:
|
elif code == AUTO_DOWNLOAD_OFF:
|
||||||
s['auto_download'] = False
|
s['auto_download'] = False
|
||||||
|
# Feeds.episode_notification: 1 = a notification for each new episode
|
||||||
|
# (0, AntennaPod's default, leaves diora's setting alone).
|
||||||
|
if ap['episode_notification']:
|
||||||
|
s['notify'] = True
|
||||||
return s, not_kept_updated
|
return s, not_kept_updated
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -769,7 +773,7 @@ class AntennaPodImport:
|
||||||
SETTING_LABELS = {
|
SETTING_LABELS = {
|
||||||
'playback_speed': 'speed', 'skip_intro': 'skip intro', 'skip_outro': 'skip outro', 'volume': 'volume',
|
'playback_speed': 'speed', 'skip_intro': 'skip intro', 'skip_outro': 'skip outro', 'volume': 'volume',
|
||||||
'include_words': 'include words', 'exclude_words': 'exclude words', 'min_duration': 'min duration',
|
'include_words': 'include words', 'exclude_words': 'exclude words', 'min_duration': 'min duration',
|
||||||
'new_episodes': 'new episodes', 'auto_download': 'auto-download',
|
'new_episodes': 'new episodes', 'auto_download': 'auto-download', 'notify': 'notifications',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
16
podcasts/migrations/0013_feed_notify.py
Normal file
16
podcasts/migrations/0013_feed_notify.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('podcasts', '0012_player_state'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='podcastfeed',
|
||||||
|
name='notify',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -51,6 +51,9 @@ class PodcastFeed(models.Model):
|
||||||
# Devices download this feed's new episodes (those in the inbox) on
|
# Devices download this feed's new episodes (those in the inbox) on
|
||||||
# their own, for listening offline.
|
# their own, for listening offline.
|
||||||
auto_download = models.BooleanField(default=False)
|
auto_download = models.BooleanField(default=False)
|
||||||
|
# Devices tell about this feed's new episodes (a notification, like
|
||||||
|
# AntennaPod's per-podcast "episode notifications").
|
||||||
|
notify = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('user', 'rss_url')
|
unique_together = ('user', 'rss_url')
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ class Backup:
|
||||||
minimal_duration_filter INTEGER DEFAULT -1, new_episodes_action INTEGER DEFAULT 0,
|
minimal_duration_filter INTEGER DEFAULT -1, new_episodes_action INTEGER DEFAULT 0,
|
||||||
feed_playback_speed REAL DEFAULT -1, feed_skip_intro INTEGER DEFAULT 0,
|
feed_playback_speed REAL DEFAULT -1, feed_skip_intro INTEGER DEFAULT 0,
|
||||||
feed_skip_ending INTEGER DEFAULT 0, feed_volume_adaption INTEGER DEFAULT 0,
|
feed_skip_ending INTEGER DEFAULT 0, feed_volume_adaption INTEGER DEFAULT 0,
|
||||||
auto_download INTEGER DEFAULT 0, state INTEGER DEFAULT 0, tags TEXT);
|
auto_download INTEGER DEFAULT 0, episode_notification INTEGER DEFAULT 0, state INTEGER DEFAULT 0,
|
||||||
|
tags TEXT);
|
||||||
CREATE TABLE FeedItems (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, pubDate INTEGER,
|
CREATE TABLE FeedItems (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, pubDate INTEGER,
|
||||||
read INTEGER, link TEXT, description TEXT, media INTEGER, feed INTEGER,
|
read INTEGER, link TEXT, description TEXT, media INTEGER, feed INTEGER,
|
||||||
item_identifier TEXT, image_url TEXT);
|
item_identifier TEXT, image_url TEXT);
|
||||||
|
|
@ -253,6 +254,26 @@ class FeedImportTests(ImportTestCase):
|
||||||
self.assertIn('default for new subscriptions: on\n', self.run_import('--global-auto-download', 'on'))
|
self.assertIn('default for new subscriptions: on\n', self.run_import('--global-auto-download', 'on'))
|
||||||
self.assertIn('default for new subscriptions: on\n', self.run_import())
|
self.assertIn('default for new subscriptions: on\n', self.run_import())
|
||||||
|
|
||||||
|
def test_episode_notifications(self):
|
||||||
|
self.diora_feed('https://example.org/diora-on', notify=True)
|
||||||
|
self.diora_feed('https://example.org/on-both', notify=True)
|
||||||
|
self.ap.feed('https://example.org/diora-on', episode_notification=0)
|
||||||
|
self.ap.feed('https://example.org/on-both', episode_notification=1)
|
||||||
|
self.ap.feed('https://example.org/on', episode_notification=1)
|
||||||
|
self.ap.feed('https://example.org/off')
|
||||||
|
out = self.run_import()
|
||||||
|
values = dict(PodcastFeed.objects.filter(user=self.user).values_list('rss_url', 'notify'))
|
||||||
|
# AntennaPod's "off" is its default: diora's own setting stays.
|
||||||
|
self.assertEqual(values, {'https://example.org/diora-on': True, 'https://example.org/on-both': True,
|
||||||
|
'https://example.org/on': True, 'https://example.org/off': False})
|
||||||
|
self.assertIn('notifications 1', out)
|
||||||
|
|
||||||
|
def test_backup_without_episode_notification_column(self):
|
||||||
|
self.ap.db.execute('ALTER TABLE Feeds DROP COLUMN episode_notification')
|
||||||
|
self.ap.feed('https://example.org/old')
|
||||||
|
self.run_import()
|
||||||
|
self.assertFalse(PodcastFeed.objects.get(user=self.user, rss_url='https://example.org/old').notify)
|
||||||
|
|
||||||
def test_created_feeds_start_with_the_users_default(self):
|
def test_created_feeds_start_with_the_users_default(self):
|
||||||
PodcastSettings.objects.create(user=self.user, auto_download_default=True)
|
PodcastSettings.objects.create(user=self.user, auto_download_default=True)
|
||||||
self.ap.feed('https://example.org/explicit-off', auto_download=0)
|
self.ap.feed('https://example.org/explicit-off', auto_download=0)
|
||||||
|
|
|
||||||
|
|
@ -972,6 +972,7 @@ class OpmlExportTests(TestCase):
|
||||||
FEED_DEFAULTS = {
|
FEED_DEFAULTS = {
|
||||||
'playback_speed': None, 'skip_intro': 0, 'skip_outro': 0, 'volume': 1.0, 'new_episodes': 'inbox',
|
'playback_speed': None, 'skip_intro': 0, 'skip_outro': 0, 'volume': 1.0, 'new_episodes': 'inbox',
|
||||||
'include_words': '', 'exclude_words': '', 'min_duration': 0, 'auto_queue': False, 'auto_download': False,
|
'include_words': '', 'exclude_words': '', 'min_duration': 0, 'auto_queue': False, 'auto_download': False,
|
||||||
|
'notify': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -997,7 +998,7 @@ class FeedSettingsTests(TestCase):
|
||||||
self.assertEqual(r.json(), {
|
self.assertEqual(r.json(), {
|
||||||
'feed_id': self.feed.id, 'playback_speed': 1.5, 'skip_intro': 30, 'skip_outro': 600, 'volume': 2.0,
|
'feed_id': self.feed.id, 'playback_speed': 1.5, 'skip_intro': 30, 'skip_outro': 600, 'volume': 2.0,
|
||||||
'new_episodes': 'inbox', 'include_words': 'News, Talk', 'exclude_words': 'Teaser', 'min_duration': 10,
|
'new_episodes': 'inbox', 'include_words': 'News, Talk', 'exclude_words': 'Teaser', 'min_duration': 10,
|
||||||
'auto_queue': False, 'auto_download': False,
|
'auto_queue': False, 'auto_download': False, 'notify': False,
|
||||||
})
|
})
|
||||||
self.feed.refresh_from_db()
|
self.feed.refresh_from_db()
|
||||||
self.assertEqual((self.feed.playback_speed, self.feed.skip_intro, self.feed.volume, self.feed.min_duration),
|
self.assertEqual((self.feed.playback_speed, self.feed.skip_intro, self.feed.volume, self.feed.min_duration),
|
||||||
|
|
@ -1023,6 +1024,7 @@ class FeedSettingsTests(TestCase):
|
||||||
('include_words', 'x' * 501), ('include_words', 5), ('exclude_words', ['a']),
|
('include_words', 'x' * 501), ('include_words', 5), ('exclude_words', ['a']),
|
||||||
('min_duration', -1), ('min_duration', 601),
|
('min_duration', -1), ('min_duration', 601),
|
||||||
('auto_download', 1), ('auto_download', 'true'), ('auto_download', None),
|
('auto_download', 1), ('auto_download', 'true'), ('auto_download', None),
|
||||||
|
('notify', 1), ('notify', 'false'), ('notify', None),
|
||||||
]
|
]
|
||||||
for name, value in bad:
|
for name, value in bad:
|
||||||
with self.subTest(name=name, value=value):
|
with self.subTest(name=name, value=value):
|
||||||
|
|
@ -1057,6 +1059,17 @@ class FeedSettingsTests(TestCase):
|
||||||
self.feed.refresh_from_db()
|
self.feed.refresh_from_db()
|
||||||
self.assertFalse(self.feed.auto_download)
|
self.assertFalse(self.feed.auto_download)
|
||||||
|
|
||||||
|
def test_notify_on_and_off(self):
|
||||||
|
self.assertIs(self.post({'notify': True}).json()['notify'], True)
|
||||||
|
self.feed.refresh_from_db()
|
||||||
|
self.assertTrue(self.feed.notify)
|
||||||
|
# Other settings (auto_download too) leave it alone.
|
||||||
|
self.assertIs(self.post({'auto_download': True}).json()['notify'], True)
|
||||||
|
self.assertIs(self.post({'notify': False}).json()['notify'], False)
|
||||||
|
self.feed.refresh_from_db()
|
||||||
|
self.assertFalse(self.feed.notify)
|
||||||
|
self.assertTrue(self.feed.auto_download)
|
||||||
|
|
||||||
def test_new_episodes_and_auto_queue_stay_in_step(self):
|
def test_new_episodes_and_auto_queue_stay_in_step(self):
|
||||||
self.assertEqual(self.post({'new_episodes': 'queue'}).json()['auto_queue'], True)
|
self.assertEqual(self.post({'new_episodes': 'queue'}).json()['auto_queue'], True)
|
||||||
self.feed.refresh_from_db()
|
self.feed.refresh_from_db()
|
||||||
|
|
@ -1085,12 +1098,12 @@ class FeedSettingsTests(TestCase):
|
||||||
|
|
||||||
def test_feeds_and_state_carry_the_settings(self):
|
def test_feeds_and_state_carry_the_settings(self):
|
||||||
self.post({'playback_speed': 1.25, 'skip_outro': 20, 'volume': 0.5, 'new_episodes': 'queue',
|
self.post({'playback_speed': 1.25, 'skip_outro': 20, 'volume': 0.5, 'new_episodes': 'queue',
|
||||||
'exclude_words': 'Trailer', 'min_duration': 5, 'auto_download': True})
|
'exclude_words': 'Trailer', 'min_duration': 5, 'auto_download': True, 'notify': True})
|
||||||
PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/2', title='Zweiter')
|
PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/2', title='Zweiter')
|
||||||
expected = {
|
expected = {
|
||||||
self.feed.id: {'feed_id': self.feed.id, **FEED_DEFAULTS, 'playback_speed': 1.25, 'skip_outro': 20,
|
self.feed.id: {'feed_id': self.feed.id, **FEED_DEFAULTS, 'playback_speed': 1.25, 'skip_outro': 20,
|
||||||
'volume': 0.5, 'new_episodes': 'queue', 'exclude_words': 'Trailer', 'min_duration': 5,
|
'volume': 0.5, 'new_episodes': 'queue', 'exclude_words': 'Trailer', 'min_duration': 5,
|
||||||
'auto_queue': True, 'auto_download': True},
|
'auto_queue': True, 'auto_download': True, 'notify': True},
|
||||||
}
|
}
|
||||||
for url in ('/podcasts/feeds/', '/podcasts/state/'):
|
for url in ('/podcasts/feeds/', '/podcasts/state/'):
|
||||||
with self.subTest(url=url):
|
with self.subTest(url=url):
|
||||||
|
|
|
||||||
|
|
@ -725,6 +725,7 @@ def feed_list(request):
|
||||||
FEED_SETTING_FIELDS = (
|
FEED_SETTING_FIELDS = (
|
||||||
'playback_speed', 'skip_intro', 'skip_outro', 'volume',
|
'playback_speed', 'skip_intro', 'skip_outro', 'volume',
|
||||||
'new_episodes', 'include_words', 'exclude_words', 'min_duration', 'auto_queue', 'auto_download',
|
'new_episodes', 'include_words', 'exclude_words', 'min_duration', 'auto_queue', 'auto_download',
|
||||||
|
'notify',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -896,6 +897,7 @@ FEED_SETTING_PARSERS = {
|
||||||
'exclude_words': _word_list,
|
'exclude_words': _word_list,
|
||||||
'min_duration': lambda v: _whole(v, 0, 600),
|
'min_duration': lambda v: _whole(v, 0, 600),
|
||||||
'auto_download': _boolean,
|
'auto_download': _boolean,
|
||||||
|
'notify': _boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue