diff --git a/tubesync/sync/models.py b/tubesync/sync/models.py index aa716a8f..671600b1 100644 --- a/tubesync/sync/models.py +++ b/tubesync/sync/models.py @@ -28,10 +28,13 @@ class Source(models.Model): ''' SOURCE_TYPE_YOUTUBE_CHANNEL = 'c' + SOURCE_TYPE_YOUTUBE_CHANNEL_ID = 'i' SOURCE_TYPE_YOUTUBE_PLAYLIST = 'p' - SOURCE_TYPES = (SOURCE_TYPE_YOUTUBE_CHANNEL, SOURCE_TYPE_YOUTUBE_PLAYLIST) + SOURCE_TYPES = (SOURCE_TYPE_YOUTUBE_CHANNEL, SOURCE_TYPE_YOUTUBE_CHANNEL_ID, + SOURCE_TYPE_YOUTUBE_PLAYLIST) SOURCE_TYPE_CHOICES = ( (SOURCE_TYPE_YOUTUBE_CHANNEL, _('YouTube channel')), + (SOURCE_TYPE_YOUTUBE_CHANNEL_ID, _('YouTube channel by ID')), (SOURCE_TYPE_YOUTUBE_PLAYLIST, _('YouTube playlist')), ) @@ -98,21 +101,25 @@ class Source(models.Model): # Fontawesome icons used for the source on the front end ICONS = { SOURCE_TYPE_YOUTUBE_CHANNEL: '', + SOURCE_TYPE_YOUTUBE_CHANNEL_ID: '', SOURCE_TYPE_YOUTUBE_PLAYLIST: '', } # Format to use to display a URL for the source URLS = { SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/c/{key}', + SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'https://www.youtube.com/channel/{key}', SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}', } # Callback functions to get a list of media from the source INDEXERS = { SOURCE_TYPE_YOUTUBE_CHANNEL: get_youtube_media_info, + SOURCE_TYPE_YOUTUBE_CHANNEL_ID: get_youtube_media_info, SOURCE_TYPE_YOUTUBE_PLAYLIST: get_youtube_media_info, } # Field names to find the media ID used as the key when storing media KEY_FIELD = { SOURCE_TYPE_YOUTUBE_CHANNEL: 'id', + SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'id', SOURCE_TYPE_YOUTUBE_PLAYLIST: 'id', } @@ -433,32 +440,39 @@ class Media(models.Model): # Format to use to display a URL for the media URLS = { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/watch?v={key}', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'https://www.youtube.com/watch?v={key}', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/watch?v={key}', } # Maps standardised names to names used in source metdata METADATA_FIELDS = { 'upload_date': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'upload_date', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'upload_date', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'upload_date', }, 'title': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'title', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'title', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'title', }, 'thumbnail': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'thumbnail', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'thumbnail', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'thumbnail', }, 'description': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'description', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'description', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'description', }, 'duration': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'duration', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'duration', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'duration', }, 'formats': { Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'formats', + Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID: 'formats', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'formats', } } diff --git a/tubesync/sync/templates/sync/sources.html b/tubesync/sync/templates/sync/sources.html index 2e974ad0..ae736799 100644 --- a/tubesync/sync/templates/sync/sources.html +++ b/tubesync/sync/templates/sync/sources.html @@ -10,10 +10,13 @@ {% include 'infobox.html' with message=message %}
-
+ - diff --git a/tubesync/sync/tests.py b/tubesync/sync/tests.py index 0b5a9272..f3f4d99f 100644 --- a/tubesync/sync/tests.py +++ b/tubesync/sync/tests.py @@ -28,6 +28,7 @@ class FrontEndTestCase(TestCase): def test_validate_source(self): test_source_types = { 'youtube-channel': Source.SOURCE_TYPE_YOUTUBE_CHANNEL, + 'youtube-channel-id': Source.SOURCE_TYPE_YOUTUBE_CHANNEL_ID, 'youtube-playlist': Source.SOURCE_TYPE_YOUTUBE_PLAYLIST, } test_sources = { @@ -36,8 +37,6 @@ class FrontEndTestCase(TestCase): 'https://www.youtube.com/testchannel', 'https://www.youtube.com/c/testchannel', 'https://www.youtube.com/c/testchannel/videos', - 'https://www.youtube.com/channel/testchannel', - 'https://www.youtube.com/channel/testchannel/videos', ), 'invalid_schema': ( 'http://www.youtube.com/c/playlist', @@ -53,13 +52,37 @@ class FrontEndTestCase(TestCase): ), 'invalid_is_playlist': ( 'https://www.youtube.com/c/playlist', - 'https://www.youtube.com/c/playlist', + ), + 'invalid_channel_with_id': ( + 'https://www.youtube.com/channel/channelid', + 'https://www.youtube.com/channel/channelid/videos', + ), + }, + 'youtube-channel-id': { + 'valid': ( + 'https://www.youtube.com/channel/channelid', + 'https://www.youtube.com/channel/channelid/videos', + ), + 'invalid_schema': ( + 'http://www.youtube.com/channel/channelid', + 'ftp://www.youtube.com/channel/channelid', + ), + 'invalid_domain': ( + 'https://www.test.com/channel/channelid', + 'https://www.example.com/channel/channelid', + ), + 'invalid_path': ( + 'https://www.youtube.com/test/invalid', + 'https://www.youtube.com/channel/test/invalid', + ), + 'invalid_is_named_channel': ( + 'https://www.youtube.com/c/testname', ), }, 'youtube-playlist': { 'valid': ( - 'https://www.youtube.com/playlist?list=testplaylist' - 'https://www.youtube.com/watch?v=testvideo&list=testplaylist' + 'https://www.youtube.com/playlist?list=testplaylist', + 'https://www.youtube.com/watch?v=testvideo&list=testplaylist', ), 'invalid_schema': ( 'http://www.youtube.com/playlist?list=testplaylist', @@ -76,6 +99,7 @@ class FrontEndTestCase(TestCase): 'invalid_is_channel': ( 'https://www.youtube.com/testchannel', 'https://www.youtube.com/c/testchannel', + 'https://www.youtube.com/channel/testchannel', ), } } @@ -86,19 +110,21 @@ class FrontEndTestCase(TestCase): response = c.get('/source-validate/invalid') self.assertEqual(response.status_code, 404) for (source_type, tests) in test_sources.items(): - for test, field in tests.items(): - source_type_char = test_source_types.get(source_type) - data = {'source_url': field, 'source_type': source_type_char} - response = c.post(f'/source-validate/{source_type}', data) - if test == 'valid': - # Valid source tests should bounce to /source-add - self.assertEqual(response.status_code, 302) - url_parts = urlsplit(response.url) - self.assertEqual(url_parts.path, '/source-add') - else: - # Invalid source tests should reload the page with an error message - self.assertEqual(response.status_code, 200) - self.assertIn('