From 5f9ad234b2aff311efc53f25c000c541b37a73a4 Mon Sep 17 00:00:00 2001 From: Ashish-Minj1290 Date: Thu, 17 Sep 2026 16:49:36 +0530 Subject: [PATCH 1/4] fix(reddit): initialize anonymous session before API access --- youtube_dl/extractor/reddit.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/reddit.py b/youtube_dl/extractor/reddit.py index 222fa0172..4d71296bb 100644 --- a/youtube_dl/extractor/reddit.py +++ b/youtube_dl/extractor/reddit.py @@ -97,14 +97,33 @@ class RedditRIE(InfoExtractor): 'only_matching': True, }] + @property + def _is_logged_in(self): + return bool(self._get_cookies('https://www.reddit.com/').get('reddit_session')) + + def _real_initialize(self): + if not self._is_logged_in: + # Reddit sets a `loid` cookie for anonymous API access. + self._request_webpage( + 'https://old.reddit.com/', None, + 'Setting up session via old reddit', 'Session request failed', fatal=False) + def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) url, video_id = mobj.group('url', 'id') video_id = self._match_id(url) - data = self._download_json( - url + '/.json', video_id)[0]['data']['children'][0]['data'] + try: + data = self._download_json( + url + '/.json', video_id, expected_status=403)[0]['data']['children'][0]['data'] + except ExtractorError as e: + if isinstance(getattr(e, 'cause', None), ValueError): + if self._is_logged_in: + raise ExtractorError( + 'Your IP address is unable to access the Reddit API', expected=True) + self.raise_login_required('Account authentication is required') + raise video_url = data['url'] From edc23b0350a13d20438012a03fcde07cbc5a4bff Mon Sep 17 00:00:00 2001 From: Ashish-Minj1290 Date: Thu, 17 Sep 2026 16:49:53 +0530 Subject: [PATCH 2/4] fix(reddit): add anonymous session fallback for API access --- youtube_dl/extractor/reddit.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/reddit.py b/youtube_dl/extractor/reddit.py index 4d71296bb..31199558a 100644 --- a/youtube_dl/extractor/reddit.py +++ b/youtube_dl/extractor/reddit.py @@ -103,7 +103,7 @@ class RedditRIE(InfoExtractor): def _real_initialize(self): if not self._is_logged_in: - # Reddit sets a `loid` cookie for anonymous API access. + # We need a 'loid' cookie to access the API anonymously. self._request_webpage( 'https://old.reddit.com/', None, 'Setting up session via old reddit', 'Session request failed', fatal=False) @@ -114,6 +114,17 @@ class RedditRIE(InfoExtractor): video_id = self._match_id(url) + # Fallback for if old reddit session request failed. + if not self._is_logged_in and not self._get_cookies('https://www.reddit.com/').get('loid'): + self._request_webpage( + 'https://www.reddit.com/svc/shreddit/%s' % mobj.group('id'), video_id, + 'Setting up session via shreddit', 'Session request failed', fatal=False, + query={ + 'seeker-session': 'false', + 'render-mode': 'partial', + 'referer': url, + }) + try: data = self._download_json( url + '/.json', video_id, expected_status=403)[0]['data']['children'][0]['data'] From 4ffb53ed4048d2a0d2424c495668d664795007d1 Mon Sep 17 00:00:00 2001 From: Ashish-Minj1290 Date: Thu, 17 Sep 2026 16:50:08 +0530 Subject: [PATCH 3/4] fix(reddit): preserve slug for anonymous session fallback --- youtube_dl/extractor/reddit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/youtube_dl/extractor/reddit.py b/youtube_dl/extractor/reddit.py index 31199558a..25c6c2ccb 100644 --- a/youtube_dl/extractor/reddit.py +++ b/youtube_dl/extractor/reddit.py @@ -50,7 +50,7 @@ class RedditIE(InfoExtractor): class RedditRIE(InfoExtractor): - _VALID_URL = r'(?Phttps?://(?:[^/]+\.)?reddit\.com/r/[^/]+/comments/(?P[^/?#&]+))' + _VALID_URL = r'(?Phttps?://(?:[^/]+\.)?reddit\.com/r/[^/]+/comments/(?P[^/?#&]+)(?:/(?P[^/?#&]+))?)' _TESTS = [{ 'url': 'https://www.reddit.com/r/videos/comments/6rrwyj/that_small_heart_attack/', 'info_dict': { @@ -110,14 +110,14 @@ class RedditRIE(InfoExtractor): def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) - url, video_id = mobj.group('url', 'id') + url, video_id, slug = mobj.group('url', 'id', 'slug') video_id = self._match_id(url) # Fallback for if old reddit session request failed. if not self._is_logged_in and not self._get_cookies('https://www.reddit.com/').get('loid'): self._request_webpage( - 'https://www.reddit.com/svc/shreddit/%s' % mobj.group('id'), video_id, + 'https://www.reddit.com/svc/shreddit/%s' % (slug or video_id), video_id, 'Setting up session via shreddit', 'Session request failed', fatal=False, query={ 'seeker-session': 'false', From fd1e44a4a2381e31fad652076f9473bcad1f9c28 Mon Sep 17 00:00:00 2001 From: Ashish-Minj1290 Date: Thu, 17 Sep 2026 16:51:36 +0530 Subject: [PATCH 4/4] test(reddit): cover anonymous session initialization fallback --- test/test_reddit.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 test/test_reddit.py diff --git a/test/test_reddit.py b/test/test_reddit.py new file mode 100644 index 000000000..090ebc965 --- /dev/null +++ b/test/test_reddit.py @@ -0,0 +1,70 @@ +from __future__ import unicode_literals + +import unittest + +from test.helper import FakeYDL +from youtube_dl.extractor.reddit import RedditRIE + + +class _TestRedditRIE(RedditRIE): + def __init__(self, downloader): + super(_TestRedditRIE, self).__init__(downloader) + self.cookies = {} + self.requests = [] + self.download_kwargs = None + self.skip_old_session_cookie = False + + def _get_cookies(self, url): + return self.cookies + + def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, + fatal=True, data=None, headers={}, query={}, expected_status=None): + self.requests.append((url_or_request, query)) + if 'old.reddit.com' in url_or_request and not self.skip_old_session_cookie: + self.cookies['loid'] = object() + elif '/svc/shreddit/' in url_or_request: + self.cookies['loid'] = object() + return True + + def _download_json(self, url, video_id, **kwargs): + self.download_kwargs = kwargs + return [{ + 'data': { + 'children': [{ + 'data': { + 'url': 'https://v.redd.it/test-video', + }, + }], + }, + }] + + def _extract_m3u8_formats(self, *args, **kwargs): + return [] + + def _extract_mpd_formats(self, *args, **kwargs): + return [] + + def _sort_formats(self, formats): + pass + + +class TestRedditSession(unittest.TestCase): + def setUp(self): + self.ie = _TestRedditRIE(FakeYDL()) + + def test_initializes_anonymous_session(self): + result = self.ie._real_extract( + 'https://www.reddit.com/r/videos/comments/abc123/example-post/') + + self.assertEqual(result['url'], 'https://v.redd.it/test-video') + self.assertEqual(self.ie.requests[0][0], 'https://old.reddit.com/') + self.assertEqual(self.ie.download_kwargs.get('expected_status'), 403) + + def test_falls_back_to_shreddit_when_old_reddit_does_not_set_loid(self): + self.ie.skip_old_session_cookie = True + self.ie._real_extract( + 'https://www.reddit.com/r/videos/comments/abc123/example-post/') + + self.assertEqual(self.ie.requests[0][0], 'https://old.reddit.com/') + self.assertIn('/svc/shreddit/example-post', self.ie.requests[1][0]) + self.assertEqual(self.ie.requests[1][1]['render-mode'], 'partial')