]> jfr.im git - yt-dlp.git/commitdiff
[BiliIntl] Add login (#2172)
authorMinePlayersPE <redacted>
Fri, 31 Dec 2021 20:09:30 +0000 (03:09 +0700)
committerGitHub <redacted>
Fri, 31 Dec 2021 20:09:30 +0000 (01:39 +0530)
and misc improvements

Authored by: MinePlayersPE

yt_dlp/extractor/bilibili.py

index 2cb01ff83b41d427c63669c2e8c3a5972e439c08..4223a80ff39e377f93e715ddafef5c892f827859 100644 (file)
@@ -1,5 +1,6 @@
 # coding: utf-8
 
+import base64
 import hashlib
 import itertools
 import functools
@@ -724,14 +725,30 @@ def _real_extract(self, url):
 
 class BiliIntlBaseIE(InfoExtractor):
     _API_URL = 'https://api.bilibili.tv/intl/gateway'
+    _NETRC_MACHINE = 'biliintl'
 
     def _call_api(self, endpoint, *args, **kwargs):
-        return self._download_json(self._API_URL + endpoint, *args, **kwargs)['data']
+        json = self._download_json(self._API_URL + endpoint, *args, **kwargs)
+        if json.get('code'):
+            if json['code'] in (10004004, 10004005, 10023006):
+                self.raise_login_required()
+            elif json['code'] == 10004001:
+                self.raise_geo_restricted()
+            else:
+                if json.get('message') and str(json['code']) != json['message']:
+                    errmsg = f'{kwargs.get("errnote", "Unable to download JSON metadata")}: {self.IE_NAME} said: {json["message"]}'
+                else:
+                    errmsg = kwargs.get('errnote', 'Unable to download JSON metadata')
+                if kwargs.get('fatal'):
+                    raise ExtractorError(errmsg)
+                else:
+                    self.report_warning(errmsg)
+        return json.get('data')
 
     def json2srt(self, json):
         data = '\n\n'.join(
             f'{i + 1}\n{srt_subtitles_timecode(line["from"])} --> {srt_subtitles_timecode(line["to"])}\n{line["content"]}'
-            for i, line in enumerate(json['body']))
+            for i, line in enumerate(json['body']) if line.get('content'))
         return data
 
     def _get_subtitles(self, ep_id):
@@ -755,16 +772,6 @@ def _get_subtitles(self, ep_id):
     def _get_formats(self, ep_id):
         video_json = self._call_api(f'/web/playurl?ep_id={ep_id}&platform=web', ep_id,
                                     note='Downloading video formats', errnote='Unable to download video formats')
-        if video_json.get('code'):
-            if video_json['code'] in (10004004, 10004005, 10023006):
-                self.raise_login_required(method='cookies')
-            elif video_json['code'] == 10004001:
-                self.raise_geo_restricted()
-            elif video_json.get('message') and str(video_json['code']) != video_json['message']:
-                raise ExtractorError(
-                    f'Unable to download video formats: {self.IE_NAME} said: {video_json["message"]}', expected=True)
-            else:
-                raise ExtractorError('Unable to download video formats')
         video_json = video_json['playurl']
         formats = []
         for vid in video_json.get('video') or []:
@@ -810,10 +817,49 @@ def _extract_ep_info(self, episode_data, ep_id):
             'extractor_key': BiliIntlIE.ie_key(),
         }
 
+    def _login(self):
+        username, password = self._get_login_info()
+        if username is None:
+            return
+
+        try:
+            from Cryptodome.PublicKey import RSA
+            from Cryptodome.Cipher import PKCS1_v1_5
+        except ImportError:
+            try:
+                from Crypto.PublicKey import RSA
+                from Crypto.Cipher import PKCS1_v1_5
+            except ImportError:
+                raise ExtractorError('pycryptodomex not found. Please install', expected=True)
+
+        key_data = self._download_json(
+            'https://passport.bilibili.tv/x/intl/passport-login/web/key?lang=en-US', None,
+            note='Downloading login key', errnote='Unable to download login key')['data']
+
+        public_key = RSA.importKey(key_data['key'])
+        password_hash = PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode('utf-8'))
+        login_post = self._download_json(
+            'https://passport.bilibili.tv/x/intl/passport-login/web/login/password?lang=en-US', None, data=urlencode_postdata({
+                'username': username,
+                'password': base64.b64encode(password_hash).decode('ascii'),
+                'keep_me': 'true',
+                's_locale': 'en_US',
+                'isTrusted': 'true'
+            }), note='Logging in', errnote='Unable to log in')
+        if login_post.get('code'):
+            if login_post.get('message'):
+                raise ExtractorError(f'Unable to log in: {self.IE_NAME} said: {login_post["message"]}', expected=True)
+            else:
+                raise ExtractorError('Unable to log in')
+
+    def _real_initialize(self):
+        self._login()
+
 
 class BiliIntlIE(BiliIntlBaseIE):
     _VALID_URL = r'https?://(?:www\.)?bili(?:bili\.tv|intl\.com)/(?:[a-z]{2}/)?play/(?P<season_id>\d+)/(?P<id>\d+)'
     _TESTS = [{
+        # Bstation page
         'url': 'https://www.bilibili.tv/en/play/34613/341736',
         'info_dict': {
             'id': '341736',
@@ -823,6 +869,7 @@ class BiliIntlIE(BiliIntlBaseIE):
             'episode_number': 2,
         }
     }, {
+        # Non-Bstation page
         'url': 'https://www.bilibili.tv/en/play/1033760/11005006',
         'info_dict': {
             'id': '11005006',
@@ -831,6 +878,17 @@ class BiliIntlIE(BiliIntlBaseIE):
             'thumbnail': r're:^https://pic\.bstarstatic\.com/ogv/.+\.png$',
             'episode_number': 3,
         }
+    }, {
+        # Subtitle with empty content
+        'url': 'https://www.bilibili.tv/en/play/1005144/10131790',
+        'info_dict': {
+            'id': '10131790',
+            'ext': 'mp4',
+            'title': 'E140 - Two Heartbeats: Kabuto\'s Trap',
+            'thumbnail': r're:^https://pic\.bstarstatic\.com/ogv/.+\.png$',
+            'episode_number': 140,
+        },
+        'skip': 'According to the copyright owner\'s request, you may only watch the video after you log in.'
     }, {
         'url': 'https://www.biliintl.com/en/play/34613/341736',
         'only_matching': True,