]> jfr.im git - yt-dlp.git/blobdiff - yt_dlp/extractor/trovo.py
[TVer] Fix extractor (#3268)
[yt-dlp.git] / yt_dlp / extractor / trovo.py
index 7d6b2b88e4fe37f6a3c057851bb71f0debed8614..65ea13ddb2e74dc64acc477ced4fb5f27dc5a8ca 100644 (file)
@@ -1,11 +1,13 @@
 # coding: utf-8
 from __future__ import unicode_literals
 
+import itertools
 import json
 
 from .common import InfoExtractor
 from ..utils import (
     ExtractorError,
+    format_field,
     int_or_none,
     str_or_none,
     try_get,
@@ -16,13 +18,18 @@ class TrovoBaseIE(InfoExtractor):
     _VALID_URL_BASE = r'https?://(?:www\.)?trovo\.live/'
     _HEADERS = {'Origin': 'https://trovo.live'}
 
+    def _call_api(self, video_id, query=None, data=None):
+        return self._download_json(
+            'https://gql.trovo.live/', video_id, query=query, data=data,
+            headers={'Accept': 'application/json'})
+
     def _extract_streamer_info(self, data):
         streamer_info = data.get('streamerInfo') or {}
         username = streamer_info.get('userName')
         return {
             'uploader': streamer_info.get('nickName'),
             'uploader_id': str_or_none(streamer_info.get('uid')),
-            'uploader_url': 'https://trovo.live/' + username if username else None,
+            'uploader_url': format_field(username, template='https://trovo.live/%s'),
         }
 
 
@@ -31,9 +38,8 @@ class TrovoIE(TrovoBaseIE):
 
     def _real_extract(self, url):
         username = self._match_id(url)
-        live_info = self._download_json(
-            'https://gql.trovo.live/', username, query={
-                'query': '''{
+        live_info = self._call_api(username, query={
+            'query': '''{
   getLiveInfo(params: {userName: "%s"}) {
     isLive
     programInfo {
@@ -52,12 +58,12 @@ def _real_extract(self, url):
     }
   }
 }''' % username,
-            })['data']['getLiveInfo']
+        })['data']['getLiveInfo']
         if live_info.get('isLive') == 0:
             raise ExtractorError('%s is offline' % username, expected=True)
         program_info = live_info['programInfo']
         program_id = program_info['id']
-        title = self._live_title(program_info['title'])
+        title = program_info['title']
 
         formats = []
         for stream_info in (program_info.get('streamInfo') or []):
@@ -103,6 +109,7 @@ class TrovoVodIE(TrovoBaseIE):
             'comments': 'mincount:8',
             'categories': ['Grand Theft Auto V'],
         },
+        'skip': '404'
     }, {
         'url': 'https://trovo.live/clip/lc-5285890810184026005',
         'only_matching': True,
@@ -110,15 +117,14 @@ class TrovoVodIE(TrovoBaseIE):
 
     def _real_extract(self, url):
         vid = self._match_id(url)
-        resp = self._download_json(
-            'https://gql.trovo.live/', vid, data=json.dumps([{
-                'query': '''{
+        resp = self._call_api(vid, data=json.dumps([{
+            'query': '''{
   batchGetVodDetailInfo(params: {vids: ["%s"]}) {
     VodDetailInfos
   }
 }''' % vid,
-            }, {
-                'query': '''{
+        }, {
+            'query': '''{
   getCommentList(params: {appInfo: {postID: "%s"}, pageSize: 1000000000, preview: {}}) {
     commentList {
       author {
@@ -132,9 +138,7 @@ def _real_extract(self, url):
     }
   }
 }''' % vid,
-            }]).encode(), headers={
-                'Content-Type': 'application/json',
-            })
+        }]).encode())
         vod_detail_info = resp[0]['data']['batchGetVodDetailInfo']['VodDetailInfos'][vid]
         vod_info = vod_detail_info['vodInfo']
         title = vod_info['title']
@@ -194,3 +198,69 @@ def _real_extract(self, url):
         }
         info.update(self._extract_streamer_info(vod_detail_info))
         return info
+
+
+class TrovoChannelBaseIE(TrovoBaseIE):
+    def _get_vod_json(self, page, uid):
+        raise NotImplementedError('This method must be implemented by subclasses')
+
+    def _entries(self, uid):
+        for page in itertools.count(1):
+            vod_json = self._get_vod_json(page, uid)
+            vods = vod_json.get('vodInfos', [])
+            for vod in vods:
+                yield self.url_result(
+                    'https://trovo.live/%s/%s' % (self._TYPE, vod.get('vid')),
+                    ie=TrovoVodIE.ie_key())
+            has_more = vod_json['hasMore']
+            if not has_more:
+                break
+
+    def _real_extract(self, url):
+        id = self._match_id(url)
+        uid = str(self._call_api(id, query={
+            'query': '{getLiveInfo(params:{userName:"%s"}){streamerInfo{uid}}}' % id
+        })['data']['getLiveInfo']['streamerInfo']['uid'])
+        return self.playlist_result(self._entries(uid), playlist_id=uid)
+
+
+class TrovoChannelVodIE(TrovoChannelBaseIE):
+    _VALID_URL = r'trovovod:(?P<id>[^\s]+)'
+    IE_DESC = 'All VODs of a trovo.live channel; "trovovod:" prefix'
+
+    _TESTS = [{
+        'url': 'trovovod:OneTappedYou',
+        'playlist_mincount': 24,
+        'info_dict': {
+            'id': '100719456',
+        },
+    }]
+
+    _QUERY = '{getChannelLtvVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s}){hasMore,vodInfos{vid}}}'
+    _TYPE = 'video'
+
+    def _get_vod_json(self, page, uid):
+        return self._call_api(uid, query={
+            'query': self._QUERY % (page, uid)
+        })['data']['getChannelLtvVideoInfos']
+
+
+class TrovoChannelClipIE(TrovoChannelBaseIE):
+    _VALID_URL = r'trovoclip:(?P<id>[^\s]+)'
+    IE_DESC = 'All Clips of a trovo.live channel; "trovoclip:" prefix'
+
+    _TESTS = [{
+        'url': 'trovoclip:OneTappedYou',
+        'playlist_mincount': 29,
+        'info_dict': {
+            'id': '100719456',
+        },
+    }]
+
+    _QUERY = '{getChannelClipVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s,albumType:VOD_CLIP_ALBUM_TYPE_LATEST}){hasMore,vodInfos{vid}}}'
+    _TYPE = 'clip'
+
+    def _get_vod_json(self, page, uid):
+        return self._call_api(uid, query={
+            'query': self._QUERY % (page, uid)
+        })['data']['getChannelClipVideoInfos']